From eac5c1f6e6be1c068e74fd6b153e5fc86456b5a7 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Sun, 2 Apr 2023 19:03:37 +0200 Subject: [PATCH 01/47] Add documentation --- scripts/agent_manager.py | 4 ++++ scripts/ai_functions.py | 5 ++++- scripts/browse.py | 6 ++++++ scripts/chat.py | 1 + scripts/commands.py | 15 +++++++++++++++ scripts/config.py | 4 ++++ scripts/data.py | 1 + scripts/execute_code.py | 1 + scripts/file_operations.py | 5 +++++ scripts/keys.py | 1 + scripts/main.py | 5 +++++ scripts/speak.py | 1 + scripts/spinner.py | 5 +++++ 13 files changed, 53 insertions(+), 1 deletion(-) diff --git a/scripts/agent_manager.py b/scripts/agent_manager.py index 9939332b..b829b219 100644 --- a/scripts/agent_manager.py +++ b/scripts/agent_manager.py @@ -7,6 +7,7 @@ agents = {} # key, (task, full_message_history, model) def create_agent(task, prompt, model): + """Create a new agent and return its key""" global next_key global agents @@ -34,6 +35,7 @@ def create_agent(task, prompt, model): def message_agent(key, message): + """Send a message to an agent and return its response""" global agents task, messages, model = agents[int(key)] @@ -57,6 +59,7 @@ def message_agent(key, message): def list_agents(): + """Return a list of all agents""" global agents # Return a list of agent keys and their tasks @@ -64,6 +67,7 @@ def list_agents(): def delete_agent(key): + """Delete an agent and return True if successful, False otherwise""" global agents try: diff --git a/scripts/ai_functions.py b/scripts/ai_functions.py index b79c4dbc..6659bb5f 100644 --- a/scripts/ai_functions.py +++ b/scripts/ai_functions.py @@ -6,7 +6,7 @@ import openai # This is a magic function that can do anything with no-code. See # https://github.com/Torantulino/AI-Functions for more info. def call_ai_function(function, args, description, model="gpt-4"): - # parse args to comma seperated string + """Calls an AI function and returns the result.""" args = ", ".join(args) messages = [ { @@ -27,6 +27,7 @@ def call_ai_function(function, args, description, model="gpt-4"): def evaluate_code(code: str) -> List[str]: + """Evaluates the given code and returns a list of suggestions for improvements.""" 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.""" @@ -39,6 +40,7 @@ def evaluate_code(code: str) -> List[str]: def improve_code(suggestions: List[str], code: str) -> str: + """Improves the provided code based on the suggestions provided, making no other changes.""" function_string = ( "def generate_improved_code(suggestions: List[str], code: str) -> str:" ) @@ -53,6 +55,7 @@ def improve_code(suggestions: List[str], code: str) -> str: def write_tests(code: str, focus: List[str]) -> str: + """Generates test cases for the existing code, focusing on specific areas if required.""" function_string = ( "def create_test_cases(code: str, focus: Optional[str] = None) -> str:" ) diff --git a/scripts/browse.py b/scripts/browse.py index af7ce576..74d01b80 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -6,6 +6,7 @@ import openai def scrape_text(url): + """Scrape text from a webpage""" response = requests.get(url) # Check if the response contains an HTTP error @@ -26,6 +27,7 @@ def scrape_text(url): def extract_hyperlinks(soup): + """Extract hyperlinks from a BeautifulSoup object""" hyperlinks = [] for link in soup.find_all('a', href=True): hyperlinks.append((link.text, link['href'])) @@ -33,6 +35,7 @@ def extract_hyperlinks(soup): def format_hyperlinks(hyperlinks): + """Format hyperlinks into a list of strings""" formatted_links = [] for link_text, link_url in hyperlinks: formatted_links.append(f"{link_text} ({link_url})") @@ -40,6 +43,7 @@ def format_hyperlinks(hyperlinks): def scrape_links(url): + """Scrape hyperlinks from a webpage""" response = requests.get(url) # Check if the response contains an HTTP error @@ -57,6 +61,7 @@ def scrape_links(url): def split_text(text, max_length=8192): + """Split text into chunks of a maximum length""" paragraphs = text.split("\n") current_length = 0 current_chunk = [] @@ -75,6 +80,7 @@ def split_text(text, max_length=8192): def summarize_text(text, is_website=True): + """Summarize text using GPT-3""" if text == "": return "Error: No text to summarize" diff --git a/scripts/chat.py b/scripts/chat.py index a406812d..56f98240 100644 --- a/scripts/chat.py +++ b/scripts/chat.py @@ -27,6 +27,7 @@ def chat_with_ai( permanent_memory, token_limit, debug=False): + """Interact with the OpenAI API, sending the prompt, user input, message history, and permanent memory.""" while True: try: """ diff --git a/scripts/commands.py b/scripts/commands.py index 61085632..dbfe36c7 100644 --- a/scripts/commands.py +++ b/scripts/commands.py @@ -12,6 +12,7 @@ cfg = Config() def get_command(response): + """Parse the response and return the command name and arguments""" try: response_json = json.loads(response) command = response_json["command"] @@ -30,6 +31,7 @@ def get_command(response): def execute_command(command_name, arguments): + """Execute the command and return the response""" try: if command_name == "google": return google_search(arguments["input"]) @@ -93,11 +95,13 @@ def execute_command(command_name, arguments): def get_datetime(): + """Return the current date and time""" return "Current date and time: " + \ datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def google_search(query, num_results=8): + """Return the results of a google search""" search_results = [] for j in browse.search(query, num_results=num_results): search_results.append(j) @@ -106,6 +110,7 @@ def google_search(query, num_results=8): def browse_website(url): + """Return the results of a google search""" summary = get_text_summary(url) links = get_hyperlinks(url) @@ -119,23 +124,27 @@ def browse_website(url): def get_text_summary(url): + """Return the results of a google search""" text = browse.scrape_text(url) summary = browse.summarize_text(text) return """ "Result" : """ + summary def get_hyperlinks(url): + """Return the results of a google search""" link_list = browse.scrape_links(url) return link_list def commit_memory(string): + """Commit a string to memory""" _text = f"""Committing memory with string "{string}" """ mem.permanent_memory.append(string) return _text def delete_memory(key): + """Delete a memory with a given key""" if key >= 0 and key < len(mem.permanent_memory): _text = "Deleting memory with key " + str(key) del mem.permanent_memory[key] @@ -147,6 +156,7 @@ def delete_memory(key): def overwrite_memory(key, string): + """Overwrite a memory with a given key""" if key >= 0 and key < len(mem.permanent_memory): _text = "Overwriting memory with key " + \ str(key) + " and string " + string @@ -159,11 +169,13 @@ def overwrite_memory(key, string): def shutdown(): + """Shut down the program""" print("Shutting down...") quit() def start_agent(name, task, prompt, model="gpt-3.5-turbo"): + """Start an agent with a given name, task, and prompt""" global cfg # Remove underscores from name @@ -187,6 +199,7 @@ def start_agent(name, task, prompt, model="gpt-3.5-turbo"): def message_agent(key, message): + """Message an agent with a given key and message""" global cfg agent_response = agents.message_agent(key, message) @@ -198,10 +211,12 @@ def message_agent(key, message): def list_agents(): + """List all agents""" return agents.list_agents() def delete_agent(key): + """Delete an agent with a given key""" result = agents.delete_agent(key) if not result: return f"Agent {key} does not exist." diff --git a/scripts/config.py b/scripts/config.py index bc7ebf71..ac145657 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -6,6 +6,7 @@ class Singleton(type): _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__( @@ -19,11 +20,14 @@ class Config(metaclass=Singleton): """ def __init__(self): + """Initialize the configuration class.""" self.continuous_mode = False self.speak_mode = False def set_continuous_mode(self, value: bool): + """Set the continuous mode value.""" self.continuous_mode = value def set_speak_mode(self, value: bool): + """Set the speak mode value.""" self.speak_mode = value diff --git a/scripts/data.py b/scripts/data.py index 19473557..b1faec87 100644 --- a/scripts/data.py +++ b/scripts/data.py @@ -1,4 +1,5 @@ def load_prompt(): + """Load the prompt from data/prompt.txt""" try: # Load the promt from data/prompt.txt with open("data/prompt.txt", "r") as prompt_file: diff --git a/scripts/execute_code.py b/scripts/execute_code.py index cfd818d4..01b454a0 100644 --- a/scripts/execute_code.py +++ b/scripts/execute_code.py @@ -3,6 +3,7 @@ import os def execute_python_file(file): + """Execute a Python file in a Docker container and return the output""" workspace_folder = "auto_gpt_workspace" if not file.endswith(".py"): diff --git a/scripts/file_operations.py b/scripts/file_operations.py index 62b3dc4b..d92709fb 100644 --- a/scripts/file_operations.py +++ b/scripts/file_operations.py @@ -9,6 +9,7 @@ if not os.path.exists(working_directory): def safe_join(base, *paths): + """Join one or more path components intelligently.""" new_path = os.path.join(base, *paths) norm_new_path = os.path.normpath(new_path) @@ -19,6 +20,7 @@ def safe_join(base, *paths): def read_file(filename): + """Read a file and return the contents""" try: filepath = safe_join(working_directory, filename) with open(filepath, "r") as f: @@ -29,6 +31,7 @@ def read_file(filename): def write_to_file(filename, text): + """Write text to a file""" try: filepath = safe_join(working_directory, filename) with open(filepath, "w") as f: @@ -39,6 +42,7 @@ def write_to_file(filename, text): def append_to_file(filename, text): + """Append text to a file""" try: filepath = safe_join(working_directory, filename) with open(filepath, "a") as f: @@ -49,6 +53,7 @@ def append_to_file(filename, text): def delete_file(filename): + """Delete a file""" try: filepath = safe_join(working_directory, filename) os.remove(filepath) diff --git a/scripts/keys.py b/scripts/keys.py index 1cd439cd..dbb3b91a 100644 --- a/scripts/keys.py +++ b/scripts/keys.py @@ -1,3 +1,4 @@ +# This file contains the API keys for the various APIs used in the project. # Get yours from: https://beta.openai.com/account/api-keys OPENAI_API_KEY = "YOUR-OPENAI-KEY" # To access your ElevenLabs API key, head to https://elevenlabs.io, you diff --git a/scripts/main.py b/scripts/main.py index d51ad0fc..298fa95c 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -14,6 +14,7 @@ from config import Config class Argument(Enum): + """This class is used to define the different arguments that can be passed""" CONTINUOUS_MODE = "continuous-mode" SPEAK_MODE = "speak-mode" @@ -25,6 +26,7 @@ def print_to_console( speak_text=False, min_typing_speed=0.05, max_typing_speed=0.01): + """Prints text to the console with a typing effect""" global cfg if speak_text and cfg.speak_mode: speak.say_text(f"{title}. {content}") @@ -44,6 +46,7 @@ def print_to_console( def print_assistant_thoughts(assistant_reply): + """Prints the assistant's thoughts to the console""" global ai_name global cfg try: @@ -102,6 +105,7 @@ def print_assistant_thoughts(assistant_reply): def construct_prompt(): + """Constructs the prompt for the AI""" global ai_name # Construct the prompt print_to_console( @@ -165,6 +169,7 @@ def construct_prompt(): def parse_arguments(): + """Parses the arguments passed to the script""" global cfg cfg.set_continuous_mode(False) cfg.set_speak_mode(False) diff --git a/scripts/speak.py b/scripts/speak.py index 13ceb8d9..db05a024 100644 --- a/scripts/speak.py +++ b/scripts/speak.py @@ -12,6 +12,7 @@ tts_headers = { def say_text(text, voice_index=0): + """Say text using ElevenLabs API""" tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format( voice_id=voices[voice_index]) diff --git a/scripts/spinner.py b/scripts/spinner.py index 2a48dfec..df39dbbd 100644 --- a/scripts/spinner.py +++ b/scripts/spinner.py @@ -5,7 +5,9 @@ import time class Spinner: + """A simple spinner class""" def __init__(self, message="Loading...", delay=0.1): + """Initialize the spinner class""" self.spinner = itertools.cycle(['-', '/', '|', '\\']) self.delay = delay self.message = message @@ -13,6 +15,7 @@ class Spinner: self.spinner_thread = None def spin(self): + """Spin the spinner""" while self.running: sys.stdout.write(next(self.spinner) + " " + self.message + "\r") sys.stdout.flush() @@ -20,11 +23,13 @@ class Spinner: sys.stdout.write('\b' * (len(self.message) + 2)) def __enter__(self): + """Start the spinner""" self.running = True self.spinner_thread = threading.Thread(target=self.spin) self.spinner_thread.start() def __exit__(self, exc_type, exc_value, exc_traceback): + """Stop the spinner""" self.running = False self.spinner_thread.join() sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r') From 5c02bfa4de5b2ef4aa3bac2a10a3b1d10f0fca9f Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Mon, 3 Apr 2023 13:38:56 +0200 Subject: [PATCH 02/47] Update .gitignore Ignore venv folder --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 4d084445..d47b8396 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ scripts/__pycache__/keys.cpython-310.pyc package-lock.json *.pyc scripts/auto_gpt_workspace/* -*.mpeg \ No newline at end of file +*.mpeg +venv/* \ No newline at end of file From 765210f0cd7a40cac8d82be55b4543fa1b17e388 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Mon, 3 Apr 2023 14:10:02 +0200 Subject: [PATCH 03/47] Add extra documentation --- scripts/ai_config.py | 6 +++++- scripts/call_ai_function.py | 1 + scripts/config.py | 6 ++++++ scripts/file_operations.py | 2 +- scripts/json_parser.py | 2 ++ scripts/llm_utils.py | 1 + scripts/main.py | 3 ++- 7 files changed, 18 insertions(+), 3 deletions(-) diff --git a/scripts/ai_config.py b/scripts/ai_config.py index 945fcfb2..42d227d0 100644 --- a/scripts/ai_config.py +++ b/scripts/ai_config.py @@ -3,7 +3,9 @@ import data class AIConfig: + """Class to store the AI's name, role, and goals.""" def __init__(self, ai_name="", ai_role="", ai_goals=[]): + """Initialize the AIConfig class""" self.ai_name = ai_name self.ai_role = ai_role self.ai_goals = ai_goals @@ -13,7 +15,7 @@ class AIConfig: @classmethod def load(cls, config_file=SAVE_FILE): - # Load variables from yaml file if it exists + """Load variables from yaml file if it exists, otherwise use defaults.""" try: with open(config_file) as file: config_params = yaml.load(file, Loader=yaml.FullLoader) @@ -27,11 +29,13 @@ class AIConfig: return cls(ai_name, ai_role, ai_goals) def save(self, config_file=SAVE_FILE): + """Save variables to yaml file.""" config = {"ai_name": self.ai_name, "ai_role": self.ai_role, "ai_goals": self.ai_goals} with open(config_file, "w") as file: yaml.dump(config, file) def construct_full_prompt(self): + """Construct the full prompt for the AI to use.""" 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.""" # Construct full prompt diff --git a/scripts/call_ai_function.py b/scripts/call_ai_function.py index 0c864b49..83c87687 100644 --- a/scripts/call_ai_function.py +++ b/scripts/call_ai_function.py @@ -6,6 +6,7 @@ from llm_utils import create_chat_completion # This is a magic function that can do anything with no-code. See # https://github.com/Torantulino/AI-Functions for more info. def call_ai_function(function, args, description, model=cfg.smart_llm_model): + """Call an AI function with the given args and description.""" # 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 seperated string diff --git a/scripts/config.py b/scripts/config.py index fa3bf7cc..d98cb698 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -51,21 +51,27 @@ class Config(metaclass=Singleton): self.speak_mode = value def set_fast_llm_model(self, value: str): + """Set the fast LLM model value.""" self.fast_llm_model = value def set_smart_llm_model(self, value: str): + """Set the smart LLM model value.""" self.smart_llm_model = value def set_fast_token_limit(self, value: int): + """Set the fast token limit value.""" self.fast_token_limit = value def set_smart_token_limit(self, value: int): + """Set the smart token limit value.""" self.smart_token_limit = value def set_openai_api_key(self, value: str): + """Set the OpenAI API key value.""" self.apiopenai_api_key_key = value def set_elevenlabs_api_key(self, value: str): + """Set the ElevenLabs API key value.""" self.elevenlabs_api_key = value diff --git a/scripts/file_operations.py b/scripts/file_operations.py index d92709fb..f58d2cbc 100644 --- a/scripts/file_operations.py +++ b/scripts/file_operations.py @@ -3,7 +3,7 @@ import os.path # Set a dedicated folder for file I/O working_directory = "auto_gpt_workspace" - +# Create the directory if it doesn't exist if not os.path.exists(working_directory): os.makedirs(working_directory) diff --git a/scripts/json_parser.py b/scripts/json_parser.py index 8154b584..edd67305 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -4,6 +4,7 @@ from config import Config cfg = Config() def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): + """Fix and parse the given JSON string.""" json_schema = """ { "command": { @@ -50,6 +51,7 @@ def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): # TODO: Make debug a global config var def fix_json(json_str: str, schema: str, debug=False) -> str: + """Fix the given JSON string to make it parseable and fully complient with the provided schema.""" # Try to fix the JSON using gpt: function_string = "def fix_json(json_str: str, schema:str=None) -> str:" args = [json_str, schema] diff --git a/scripts/llm_utils.py b/scripts/llm_utils.py index 41f39625..c512e997 100644 --- a/scripts/llm_utils.py +++ b/scripts/llm_utils.py @@ -6,6 +6,7 @@ openai.api_key = cfg.openai_api_key # Overly simple abstraction until we create something better def create_chat_completion(messages, model=None, temperature=None, max_tokens=None)->str: + """Create a chat completion using the OpenAI API.""" response = openai.ChatCompletion.create( model=model, messages=messages, diff --git a/scripts/main.py b/scripts/main.py index b4236c52..b0aef178 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -117,7 +117,7 @@ def print_assistant_thoughts(assistant_reply): print_to_console("Error: \n", Fore.RED, call_stack) def load_variables(config_file="config.yaml"): - # Load variables from yaml file if it exists + """Load variables from yaml file if it exists, otherwise prompt the user for input""" try: with open(config_file) as file: config = yaml.load(file, Loader=yaml.FullLoader) @@ -200,6 +200,7 @@ Continue (y/n): """) def prompt_user(): + """Prompt the user for input""" ai_name = "" # Construct the prompt print_to_console( From 5ced5cae3aad0e2a399e24e2671cc12e0f18a6c8 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Mon, 3 Apr 2023 14:10:15 +0200 Subject: [PATCH 04/47] Update Dockerfile The requirements file wasn't on the file system. --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index c67291c8..d2a127c7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.11 WORKDIR /app COPY scripts/ /app - +COPY requirements.txt /app/requirements.txt RUN pip install -r requirements.txt CMD ["python", "main.py"] From 301ff85fd57dd018b7446798b924073cbee3eaf1 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 00:15:17 +0200 Subject: [PATCH 05/47] Update main.py Introduce check to OPENAI_API_KEY --- scripts/main.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/scripts/main.py b/scripts/main.py index 4e799743..e334753f 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -17,6 +17,16 @@ import traceback import yaml import argparse +def check_openai_api_key(): + """Check if the OpenAI API key is set in config.py or as an environment variable.""" + if not cfg.openai_api_key: + print( + Fore.RED + + "Please set your OpenAI API key in config.py or as an environment variable." + ) + print("You can get your key from https://beta.openai.com/account/api-keys") + exit(1) + def print_to_console( title, @@ -275,7 +285,7 @@ def parse_arguments(): # TODO: fill in llm values here - +check_openai_api_key() cfg = Config() parse_arguments() ai_name = "" From c74ad984cfa755031a85296c45a8a8d77e0b8906 Mon Sep 17 00:00:00 2001 From: Malik M Alnakhaleh Date: Mon, 3 Apr 2023 20:54:12 -0400 Subject: [PATCH 06/47] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5848c585..284267db 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ python scripts/main.py --speak ## 🔍 Google API Keys Configuration -This section is optional, use the official google api if you are having issues with error 429 when running google search. +This section is optional, use the official google api if you are having issues with error 429 when running a google search. To use the `google_official_search` command, you need to set up your Google API keys in your environment variables. 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). From b6344b98e241eb2d9607a54d9a4d9efc09f7bb8f Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:46:02 +0200 Subject: [PATCH 07/47] Update main.py Add separation between code blocks. --- scripts/main.py | 41 ++++++++++++++++++++++++++++++++++------- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/scripts/main.py b/scripts/main.py index 0ec8978f..8f0400ae 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -27,17 +27,24 @@ def print_to_console( max_typing_speed=0.01): """Prints text to the console with a typing effect""" global cfg + if speak_text and cfg.speak_mode: speak.say_text(f"{title}. {content}") + print(title_color + title + " " + Style.RESET_ALL, end="") + if content: + if isinstance(content, list): content = " ".join(content) words = content.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 @@ -73,6 +80,7 @@ def print_assistant_thoughts(assistant_reply): if assistant_thoughts_plan: print_to_console("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): @@ -80,6 +88,7 @@ def print_assistant_thoughts(assistant_reply): # Split the input_string using the newline character and dashes lines = assistant_thoughts_plan.split('\n') + for line in lines: line = line.lstrip("- ") print_to_console("- ", Fore.GREEN, line.strip()) @@ -114,11 +123,13 @@ def load_variables(config_file="config.yaml"): # Prompt the user for input if config file is missing or empty values if not ai_name: ai_name = input("Name your AI: ") + if ai_name == "": ai_name = "Entrepreneur-GPT" if not ai_role: ai_role = input(f"{ai_name} is: ") + if ai_role == "": ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth." @@ -127,16 +138,20 @@ def load_variables(config_file="config.yaml"): print("For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") print("Enter nothing to load defaults, enter nothing when finished.") ai_goals = [] + for i in range(5): ai_goal = input(f"Goal {i+1}: ") + if ai_goal == "": break ai_goals.append(ai_goal) + if len(ai_goals) == 0: ai_goals = ["Increase net worth", "Grow Twitter Account", "Develop and manage multiple businesses autonomously"] # Save variables to yaml file config = {"ai_name": ai_name, "ai_role": ai_role, "ai_goals": ai_goals} + with open(config_file, "w") as file: documents = yaml.dump(config, file) @@ -145,6 +160,7 @@ def load_variables(config_file="config.yaml"): # Construct full prompt full_prompt = f"You are {ai_name}, {ai_role}\n{prompt_start}\n\nGOALS:\n\n" + for i, goal in enumerate(ai_goals): full_prompt += f"{i+1}. {goal}\n" @@ -155,6 +171,7 @@ def load_variables(config_file="config.yaml"): def construct_prompt(): """Construct the prompt for the AI to respond to""" config = AIConfig.load() + if config.ai_name: print_to_console( f"Welcome back! ", @@ -162,10 +179,11 @@ def construct_prompt(): f"Would you like me to return to being {config.ai_name}?", speak_text=True) should_continue = input(f"""Continue with the last settings? -Name: {config.ai_name} -Role: {config.ai_role} -Goals: {config.ai_goals} -Continue (y/n): """) + Name: {config.ai_name} + Role: {config.ai_role} + Goals: {config.ai_goals} + Continue (y/n): """) + if should_continue.lower() == "n": config = AIConfig() @@ -196,7 +214,9 @@ def prompt_user(): "Name your AI: ", Fore.GREEN, "For example, 'Entrepreneur-GPT'") + ai_name = input("AI Name: ") + if ai_name == "": ai_name = "Entrepreneur-GPT" @@ -212,6 +232,7 @@ def prompt_user(): Fore.GREEN, "For example, 'an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth.'") ai_role = input(f"{ai_name} is: ") + if ai_role == "": ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth." @@ -220,13 +241,18 @@ def prompt_user(): "Enter up to 5 goals for your AI: ", Fore.GREEN, "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") + print("Enter nothing to load defaults, enter nothing when finished.", flush=True) ai_goals = [] + for i in range(5): ai_goal = input(f"{Fore.LIGHTBLUE_EX}Goal{Style.RESET_ALL} {i+1}: ") + if ai_goal == "": break + ai_goals.append(ai_goal) + if len(ai_goals) == 0: ai_goals = ["Increase net worth", "Grow Twitter Account", "Develop and manage multiple businesses autonomously"] @@ -234,6 +260,7 @@ def prompt_user(): config = AIConfig(ai_name, ai_role, ai_goals) return config + def parse_arguments(): """Parses the arguments passed to the script""" global cfg @@ -307,11 +334,11 @@ while True: "NEXT ACTION: ", Fore.CYAN, f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}") - print( - "Enter 'y' to authorise command or 'n' to exit program...", - flush=True) + print("Enter 'y' to authorise command or 'n' to exit program...", flush=True) + while True: console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL) + if console_input.lower() == "y": user_input = "GENERATE NEXT COMMAND JSON" break From cd164648ba8a9368bde00d5812a96ed569ea0d76 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:47:04 +0200 Subject: [PATCH 08/47] Update ai_config.py Add separation between code blocks. --- scripts/ai_config.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/ai_config.py b/scripts/ai_config.py index 42d227d0..0b521b7d 100644 --- a/scripts/ai_config.py +++ b/scripts/ai_config.py @@ -13,6 +13,7 @@ class AIConfig: # Soon this will go in a folder where it remembers more stuff about the run(s) SAVE_FILE = "last_run_ai_settings.yaml" + @classmethod def load(cls, config_file=SAVE_FILE): """Load variables from yaml file if it exists, otherwise use defaults.""" @@ -28,18 +29,21 @@ class AIConfig: return cls(ai_name, ai_role, ai_goals) + def save(self, config_file=SAVE_FILE): """Save variables to yaml file.""" config = {"ai_name": self.ai_name, "ai_role": self.ai_role, "ai_goals": self.ai_goals} with open(config_file, "w") as file: yaml.dump(config, file) + def construct_full_prompt(self): """Construct the full prompt for the AI to use.""" 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.""" # Construct full prompt full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n" + for i, goal in enumerate(self.ai_goals): full_prompt += f"{i+1}. {goal}\n" From 27bf7c47e98a3c8fbb660b6fa00f6d61771863d4 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:48:19 +0200 Subject: [PATCH 09/47] Update ai_functions.py Unnecessary spaces --- scripts/ai_functions.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/ai_functions.py b/scripts/ai_functions.py index eb524b9b..f93d7ea6 100644 --- a/scripts/ai_functions.py +++ b/scripts/ai_functions.py @@ -6,7 +6,6 @@ from json_parser import fix_and_parse_json cfg = Config() # Evaluating code - def evaluate_code(code: str) -> List[str]: """Evaluates the given code and returns a list of suggestions for improvements.""" function_string = "def analyze_code(code: str) -> List[str]:" @@ -19,7 +18,6 @@ def evaluate_code(code: str) -> List[str]: # Improving code - def improve_code(suggestions: List[str], code: str) -> str: """Improves the provided code based on the suggestions provided, making no other changes.""" function_string = ( @@ -33,8 +31,6 @@ def improve_code(suggestions: List[str], code: str) -> str: # Writing tests - - def write_tests(code: str, focus: List[str]) -> str: """Generates test cases for the existing code, focusing on specific areas if required.""" function_string = ( From 1b4a5301d0d4406732b8e903af1bfda8e7552a6d Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:49:28 +0200 Subject: [PATCH 10/47] Update ai_functions.py Just import dumps from JSON library. --- scripts/ai_functions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/ai_functions.py b/scripts/ai_functions.py index f93d7ea6..eb6dbd93 100644 --- a/scripts/ai_functions.py +++ b/scripts/ai_functions.py @@ -1,5 +1,5 @@ from typing import List, Optional -import json +from json import dumps from config import Config from call_ai_function import call_ai_function from json_parser import fix_and_parse_json @@ -23,7 +23,7 @@ def improve_code(suggestions: List[str], code: str) -> str: function_string = ( "def generate_improved_code(suggestions: List[str], code: str) -> str:" ) - args = [json.dumps(suggestions), code] + args = [dumps(suggestions), code] description_string = """Improves the provided code based on the suggestions provided, making no other changes.""" result_string = call_ai_function(function_string, args, description_string) @@ -36,7 +36,7 @@ def write_tests(code: str, focus: List[str]) -> str: function_string = ( "def create_test_cases(code: str, focus: Optional[str] = None) -> str:" ) - args = [code, json.dumps(focus)] + args = [code, dumps(focus)] description_string = """Generates test cases for the existing code, focusing on specific areas if required.""" result_string = call_ai_function(function_string, args, description_string) From 39f758ba5ceaac927c50f3bacb94e4b24c5f0706 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:53:47 +0200 Subject: [PATCH 11/47] Update browse.py Add separation between code blocks and standardize dictionary definition. --- scripts/browse.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/scripts/browse.py b/scripts/browse.py index 8a10ddd6..85f7bc6c 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -5,6 +5,7 @@ from llm_utils import create_chat_completion cfg = Config() + def scrape_text(url): """Scrape text from a webpage""" response = requests.get(url) @@ -29,16 +30,20 @@ def scrape_text(url): def extract_hyperlinks(soup): """Extract hyperlinks from a BeautifulSoup object""" hyperlinks = [] + for link in soup.find_all('a', href=True): hyperlinks.append((link.text, link['href'])) + return hyperlinks def format_hyperlinks(hyperlinks): """Format hyperlinks into a list of strings""" formatted_links = [] + for link_text, link_url in hyperlinks: formatted_links.append(f"{link_text} ({link_url})") + return formatted_links @@ -67,6 +72,7 @@ def split_text(text, max_length=8192): current_chunk = [] for paragraph in paragraphs: + if current_length + len(paragraph) + 1 <= max_length: current_chunk.append(paragraph) current_length += len(paragraph) + 1 @@ -90,19 +96,22 @@ def summarize_text(text, is_website=True): for i, chunk in enumerate(chunks): print("Summarizing chunk " + str(i + 1) + " / " + str(len(chunks))) + if is_website: messages = [ { "role": "user", "content": "Please summarize the following website text, do not describe the general website, but instead concisely extract the specific information this subpage contains.: " + - chunk}, + chunk + } ] else: messages = [ { "role": "user", "content": "Please summarize the following text, focusing on extracting concise and specific information: " + - chunk}, + chunk + } ] summary = create_chat_completion( @@ -121,14 +130,16 @@ def summarize_text(text, is_website=True): { "role": "user", "content": "Please summarize the following website text, do not describe the general website, but instead concisely extract the specific information this subpage contains.: " + - combined_summary}, + combined_summary + } ] else: messages = [ { "role": "user", "content": "Please summarize the following text, focusing on extracting concise and specific infomation: " + - combined_summary}, + combined_summary + } ] final_summary = create_chat_completion( From 772d4beb218e66b38347a265640d15dc8bc304b6 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:54:52 +0200 Subject: [PATCH 12/47] Update browse.py Just import GET from requests library. --- scripts/browse.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/browse.py b/scripts/browse.py index 85f7bc6c..20ffc035 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -1,4 +1,4 @@ -import requests +from requests import get from bs4 import BeautifulSoup from config import Config from llm_utils import create_chat_completion @@ -8,7 +8,7 @@ cfg = Config() def scrape_text(url): """Scrape text from a webpage""" - response = requests.get(url) + response = get(url) # Check if the response contains an HTTP error if response.status_code >= 400: @@ -49,7 +49,7 @@ def format_hyperlinks(hyperlinks): def scrape_links(url): """Scrape hyperlinks from a webpage""" - response = requests.get(url) + response = get(url) # Check if the response contains an HTTP error if response.status_code >= 400: From 1b7b367ce9e0455caf7ec2efbb0b5f4701bd2a51 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:56:56 +0200 Subject: [PATCH 13/47] Update call_ai_function.py Standardize importation of libraries to top. --- scripts/call_ai_function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/call_ai_function.py b/scripts/call_ai_function.py index 83c87687..44f9fd8c 100644 --- a/scripts/call_ai_function.py +++ b/scripts/call_ai_function.py @@ -1,7 +1,7 @@ from config import Config +from llm_utils import create_chat_completion cfg = Config() -from llm_utils import create_chat_completion # This is a magic function that can do anything with no-code. See # https://github.com/Torantulino/AI-Functions for more info. From 238824553ae8626af1e8e27218d947a859a220ac Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 10:58:38 +0200 Subject: [PATCH 14/47] Update chat.py Standardize importation of libraries to top. --- scripts/chat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/chat.py b/scripts/chat.py index c526be15..819e0881 100644 --- a/scripts/chat.py +++ b/scripts/chat.py @@ -3,10 +3,8 @@ import openai from dotenv import load_dotenv from config import Config import token_counter - -cfg = Config() - from llm_utils import create_chat_completion +cfg = Config() def create_chat_message(role, content): @@ -50,8 +48,10 @@ def chat_with_ai( """ model = cfg.fast_llm_model # TODO: Change model from hardcode to argument # Reserve 1000 tokens for the response + if debug: print(f"Token limit: {token_limit}") + send_token_limit = token_limit - 1000 current_context = [ From 500b2b48367566708829a9f5ce4eb4c4d9c4acde Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:02:01 +0200 Subject: [PATCH 15/47] Update chat.py Adds code separation between code blocks. --- scripts/chat.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/chat.py b/scripts/chat.py index 819e0881..dd52e784 100644 --- a/scripts/chat.py +++ b/scripts/chat.py @@ -73,6 +73,7 @@ def chat_with_ai( 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 @@ -98,13 +99,16 @@ def chat_with_ai( print(f"Send Token Count: {current_tokens_used}") print(f"Tokens remaining for response: {tokens_remaining}") print("------------ CONTEXT SENT TO AI ---------------") + for message in current_context: # Skip printing the prompt + if message["role"] == "system" and message["content"] == prompt: continue print( f"{message['role'].capitalize()}: {message['content']}") print() + print("----------- END OF CONTEXT ----------------") # TODO: use a model defined elsewhere, so that model can contain temperature and other settings we care about From 9699a1ca78a18188c45ba09bc6b07f69624bd8bc Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:03:09 +0200 Subject: [PATCH 16/47] Update chat.py Just import sleep from time library. --- scripts/chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/chat.py b/scripts/chat.py index dd52e784..17e36879 100644 --- a/scripts/chat.py +++ b/scripts/chat.py @@ -1,4 +1,4 @@ -import time +from time import sleep import openai from dotenv import load_dotenv from config import Config @@ -130,4 +130,4 @@ def chat_with_ai( except openai.error.RateLimitError: # TODO: WHen we switch to langchain, this is built in print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...") - time.sleep(10) + sleep(10) From 632d87c195ed52da45d4150c8d6eb64bbc96f243 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:28:15 +0200 Subject: [PATCH 17/47] Update commands.py Just import datetime from datetime library. --- scripts/commands.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/commands.py b/scripts/commands.py index 42fe1ebb..7873c048 100644 --- a/scripts/commands.py +++ b/scripts/commands.py @@ -1,7 +1,7 @@ import browse import json import memory as mem -import datetime +from datetime import datetime import agent_manager as agents import speak from config import Config @@ -110,7 +110,7 @@ def execute_command(command_name, arguments): def get_datetime(): """Return the current date and time""" return "Current date and time: " + \ - datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + datetime.now().strftime("%Y-%m-%d %H:%M:%S") def google_search(query, num_results=8): @@ -122,6 +122,7 @@ def google_search(query, num_results=8): return json.dumps(search_results, ensure_ascii=False, indent=4) def google_official_search(query, num_results=8): + """Return the results of a google search using the official Google API""" from googleapiclient.discovery import build from googleapiclient.errors import HttpError import json From 8053ecd5c6f7c2ce7a3363acdb31063b8d91f5af Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:29:02 +0200 Subject: [PATCH 18/47] Update config.py Introduce spaces between code blocks. --- scripts/config.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/scripts/config.py b/scripts/config.py index c7d04bf8..0650535f 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -44,42 +44,52 @@ class Config(metaclass=Singleton): # Initialize the OpenAI API client openai.api_key = self.openai_api_key + def set_continuous_mode(self, value: bool): """Set the continuous mode value.""" self.continuous_mode = value + def set_speak_mode(self, value: bool): """Set the speak mode value.""" self.speak_mode = value + def set_fast_llm_model(self, value: str): """Set the fast LLM model value.""" self.fast_llm_model = value + def set_smart_llm_model(self, value: str): """Set the smart LLM model value.""" self.smart_llm_model = value + def set_fast_token_limit(self, value: int): """Set the fast token limit value.""" self.fast_token_limit = value + def set_smart_token_limit(self, value: int): """Set the smart token limit value.""" self.smart_token_limit = value + def set_openai_api_key(self, value: str): """Set the OpenAI API key value.""" self.openai_api_key = value - + + def set_elevenlabs_api_key(self, value: str): """Set the ElevenLabs API key value.""" self.elevenlabs_api_key = value - + + def set_google_api_key(self, value: str): """Set the Google API key value.""" self.google_api_key = value - + + def set_custom_search_engine_id(self, value: str): """Set the custom search engine ID value.""" - self.custom_search_engine_id = value \ No newline at end of file + self.custom_search_engine_id = value From 1632f7ebf6332642ceedd97587dad6f76bc46740 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:30:20 +0200 Subject: [PATCH 19/47] Update data.py Just import path from OS library. --- scripts/data.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/data.py b/scripts/data.py index 22393844..bb8936c1 100644 --- a/scripts/data.py +++ b/scripts/data.py @@ -1,4 +1,4 @@ -import os +from os import path from pathlib import Path @@ -6,7 +6,7 @@ def load_prompt(): """Load the prompt from data/prompt.txt""" try: # get directory of this file: - file_dir = Path(os.path.dirname(os.path.realpath(__file__))) + file_dir = Path(path.dirname(path.realpath(__file__))) data_dir = file_dir / "data" prompt_file = data_dir / "prompt.txt" # Load the promt from data/prompt.txt From d450ac3a0b489bf820d2c3794b08955054d1f543 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:31:20 +0200 Subject: [PATCH 20/47] Update execute_code.py Just import path from OS library. --- scripts/execute_code.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/execute_code.py b/scripts/execute_code.py index f34469dd..22ae1047 100644 --- a/scripts/execute_code.py +++ b/scripts/execute_code.py @@ -1,5 +1,5 @@ import docker -import os +from os import path def execute_python_file(file): @@ -11,9 +11,9 @@ def execute_python_file(file): if not file.endswith(".py"): return "Error: Invalid file type. Only .py files are allowed." - file_path = os.path.join(workspace_folder, file) + file_path = path.join(workspace_folder, file) - if not os.path.isfile(file_path): + if not path.isfile(file_path): return f"Error: File '{file}' does not exist." try: From 1d10236a63e4535a7a16aa01f500d9932760f651 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:32:55 +0200 Subject: [PATCH 21/47] Update file_operations.py Introduces spaces between code blocks. --- scripts/file_operations.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/file_operations.py b/scripts/file_operations.py index 65664e60..cb054616 100644 --- a/scripts/file_operations.py +++ b/scripts/file_operations.py @@ -3,6 +3,7 @@ import os.path # Set a dedicated folder for file I/O working_directory = "auto_gpt_workspace" + # Create the directory if it doesn't exist if not os.path.exists(working_directory): os.makedirs(working_directory) @@ -35,8 +36,10 @@ def write_to_file(filename, text): try: filepath = safe_join(working_directory, filename) directory = os.path.dirname(filepath) + if not os.path.exists(directory): os.makedirs(directory) + with open(filepath, "w") as f: f.write(text) return "File written to successfully." From 621f18eba79f8a31aed2da88bc9b26007c21f1ec Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:34:28 +0200 Subject: [PATCH 22/47] Update json_parser.py Introduces spaces between code blocks. --- scripts/json_parser.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/json_parser.py b/scripts/json_parser.py index 5153f71b..e827b753 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -1,6 +1,7 @@ import json from call_ai_function import call_ai_function from config import Config + cfg = Config() def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): @@ -37,15 +38,18 @@ def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): json_str = json_str[:last_brace_index+1] return json.loads(json_str) except Exception as e: + if try_to_fix_with_gpt: print(f"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 = fix_json(json_str, json_schema, False) + if ai_fixed_json != "failed": return json.loads(ai_fixed_json) else: print(f"Failed to fix ai output, telling the AI.") # This allows the AI to react to the error message, which usually results in it correcting its ways. return json_str + else: raise e @@ -59,15 +63,18 @@ def fix_json(json_str: str, schema: str, debug=False) -> str: # If it doesn't already start with a "`", add one: if not json_str.startswith("`"): json_str = "```json\n" + json_str + "\n```" + result_string = call_ai_function( function_string, args, description_string, model=cfg.fast_llm_model ) + if debug: print("------------ JSON FIX ATTEMPT ---------------") print(f"Original JSON: {json_str}") print("-----------") print(f"Fixed JSON: {result_string}") print("----------- END OF FIX ATTEMPT ----------------") + try: return json.loads(result_string) except: From 62615cacc9532bcae1fe94d4ef27e10610edd758 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Tue, 4 Apr 2023 11:36:50 +0200 Subject: [PATCH 23/47] Adds necessary spaces Introduces spaces between code blocks. --- scripts/llm_utils.py | 1 + scripts/speak.py | 4 ++-- scripts/spinner.py | 3 +++ scripts/token_counter.py | 6 ++++++ 4 files changed, 12 insertions(+), 2 deletions(-) diff --git a/scripts/llm_utils.py b/scripts/llm_utils.py index c512e997..2981c8a0 100644 --- a/scripts/llm_utils.py +++ b/scripts/llm_utils.py @@ -1,5 +1,6 @@ import openai from config import Config + cfg = Config() openai.api_key = cfg.openai_api_key diff --git a/scripts/speak.py b/scripts/speak.py index 1e0eb408..5a0728a2 100644 --- a/scripts/speak.py +++ b/scripts/speak.py @@ -2,6 +2,7 @@ import os from playsound import playsound import requests from config import Config + cfg = Config() # TODO: Nicer names for these ids @@ -18,8 +19,7 @@ def say_text(text, voice_index=0): voice_id=voices[voice_index]) formatted_message = {"text": text} - response = requests.post( - tts_url, headers=tts_headers, json=formatted_message) + response = requests.post(tts_url, headers=tts_headers, json=formatted_message) if response.status_code == 200: with open("speech.mpeg", "wb") as f: diff --git a/scripts/spinner.py b/scripts/spinner.py index df39dbbd..2b79ae26 100644 --- a/scripts/spinner.py +++ b/scripts/spinner.py @@ -14,6 +14,7 @@ class Spinner: self.running = False self.spinner_thread = None + def spin(self): """Spin the spinner""" while self.running: @@ -22,12 +23,14 @@ class Spinner: time.sleep(self.delay) sys.stdout.write('\b' * (len(self.message) + 2)) + def __enter__(self): """Start the spinner""" self.running = True self.spinner_thread = threading.Thread(target=self.spin) self.spinner_thread.start() + def __exit__(self, exc_type, exc_value, exc_traceback): """Stop the spinner""" self.running = False diff --git a/scripts/token_counter.py b/scripts/token_counter.py index a28a9868..fc5b9c51 100644 --- a/scripts/token_counter.py +++ b/scripts/token_counter.py @@ -1,6 +1,7 @@ import tiktoken from typing import List, Dict + def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5-turbo-0301") -> int: """ Returns the number of tokens used by a list of messages. @@ -17,6 +18,7 @@ def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5 except KeyError: print("Warning: model not found. Using cl100k_base encoding.") encoding = tiktoken.get_encoding("cl100k_base") + if model == "gpt-3.5-turbo": # !Node: 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") @@ -32,15 +34,19 @@ def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5 else: raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. 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. From 795fa3e1fe9da00f824f86faf2a62b4560932b4a Mon Sep 17 00:00:00 2001 From: Breval Le Floch Date: Tue, 4 Apr 2023 14:28:34 +0000 Subject: [PATCH 24/47] patch readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d66a6022..143846b4 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ git clone https://github.com/Torantulino/Auto-GPT.git 2. Navigate to the project directory: *(Type this into your CMD window, you're aiming to navigate the CMD window to the repository you just downloaded)* ``` -$ cd 'Auto-GPT' +cd 'Auto-GPT' ``` 3. Install the required dependencies: From 23ed4b3d2faee8a8b8854ad8b7f99e08462680ad Mon Sep 17 00:00:00 2001 From: Gershon Bialer Date: Tue, 4 Apr 2023 20:36:29 -0700 Subject: [PATCH 25/47] Make sure to copy over requirements.txt to Dockerfile. --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index c67291c8..146a3747 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM python:3.11 WORKDIR /app COPY scripts/ /app +COPY requirements.txt /app RUN pip install -r requirements.txt From a090c089f17aa29752a4a5e0e0ab16ec549a789a Mon Sep 17 00:00:00 2001 From: Monkee1337 Date: Thu, 6 Apr 2023 15:30:11 +0200 Subject: [PATCH 26/47] changed order of pinecone explaination --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a89c5d03..f6d06f34 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,12 @@ are loaded for the agent at any given time. 3. Find your API key and region under the default project in the left sidebar. ### Setting up environment variables - For Windows Users: + +Simply set them in the `.env` file. + +Alternatively you can use the appropriate following method (advanced): + +For Windows Users: ``` setx PINECONE_API_KEY "YOUR_PINECONE_API_KEY" export PINECONE_ENV="Your pinecone region" # something like: us-east4-gcp @@ -163,7 +168,6 @@ export PINECONE_ENV="Your pinecone region" # something like: us-east4-gcp ``` -Or you can set them in the `.env` file. ## View Memory Usage From c1326469b206d7fb68eac0f753c925358c30675e Mon Sep 17 00:00:00 2001 From: Dino Hensen Date: Thu, 6 Apr 2023 17:15:40 +0200 Subject: [PATCH 27/47] The function default serialized gpt-4 value at import time, leading to yield a value of gpt-4 after setting gpt3only when not passing a value for model to the function when calling it, this fixes it --- scripts/call_ai_function.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/call_ai_function.py b/scripts/call_ai_function.py index 0c864b49..db1c9556 100644 --- a/scripts/call_ai_function.py +++ b/scripts/call_ai_function.py @@ -5,7 +5,9 @@ from llm_utils import create_chat_completion # This is a magic function that can do anything with no-code. See # https://github.com/Torantulino/AI-Functions for more info. -def call_ai_function(function, args, description, model=cfg.smart_llm_model): +def call_ai_function(function, args, description, model=None): + 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 seperated string From 1e9ac953a7395c756cee281e3f308e705442bfd2 Mon Sep 17 00:00:00 2001 From: Dr33dM3 <109325141+Dr33dM3@users.noreply.github.com> Date: Thu, 6 Apr 2023 10:43:29 -0700 Subject: [PATCH 28/47] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a89c5d03..22630673 100644 --- a/README.md +++ b/README.md @@ -186,6 +186,7 @@ If you don't have access to the GPT4 api, this mode will allow you to use Auto-G ``` python scripts/main.py --gpt3only ``` +For your safety we recommend using a VM(Virtual Machine) so your device may not get bricked as continous mode can potenially be dangerous for your computer. ## ⚠️ Limitations This experiment aims to showcase the potential of GPT-4 but comes with some limitations: From c1034df2d3edb2d95807f1c1ffe0496e9f1e2046 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Fri, 7 Apr 2023 17:25:22 +0900 Subject: [PATCH 29/47] fix typo in json_parser.py specifed -> specified --- scripts/json_parser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/json_parser.py b/scripts/json_parser.py index 8ec9238b..f44cf4d3 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -53,7 +53,7 @@ def fix_json(json_str: str, schema: str, debug=False) -> str: # Try to fix the JSON using gpt: function_string = "def fix_json(json_str: str, schema:str=None) -> str:" args = [f"'''{json_str}'''", f"'''{schema}'''"] - description_string = """Fixes the provided JSON string to make it parseable and fully complient with the provided schema.\n If an object or field specifed in the schema isn't contained within the correct JSON, it is ommited.\n This function is brilliant at guessing when the format is incorrect.""" + description_string = """Fixes the provided JSON string to make it parseable and fully complient with the provided schema.\n If an object or field specified in the schema isn't contained within the correct JSON, it is ommited.\n This function is brilliant at guessing when the format is incorrect.""" # If it doesn't already start with a "`", add one: if not json_str.startswith("`"): @@ -75,4 +75,4 @@ def fix_json(json_str: str, schema: str, debug=False) -> str: # import traceback # call_stack = traceback.format_exc() # print(f"Failed to fix JSON: '{json_str}' "+call_stack) - return "failed" \ No newline at end of file + return "failed" From ae97790cecc8a2aeafe65e6d19d17c4aae829c73 Mon Sep 17 00:00:00 2001 From: Monkee1337 <118180627+Monkee1337@users.noreply.github.com> Date: Fri, 7 Apr 2023 18:17:20 +0200 Subject: [PATCH 30/47] Update README.md Co-authored-by: Arlo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f6d06f34..e3fb9813 100644 --- a/README.md +++ b/README.md @@ -153,7 +153,7 @@ are loaded for the agent at any given time. Simply set them in the `.env` file. -Alternatively you can use the appropriate following method (advanced): +Alternatively, you can set them from the command line (advanced): For Windows Users: ``` From 47b097708c4916ba8d506efeba905f835410e12d Mon Sep 17 00:00:00 2001 From: Dr33dM3 <109325141+Dr33dM3@users.noreply.github.com> Date: Sat, 8 Apr 2023 18:28:09 -0700 Subject: [PATCH 31/47] Update README.md Changed the wording for the VM to make it more user friendly --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 22630673..f7bea062 100644 --- a/README.md +++ b/README.md @@ -186,7 +186,7 @@ If you don't have access to the GPT4 api, this mode will allow you to use Auto-G ``` python scripts/main.py --gpt3only ``` -For your safety we recommend using a VM(Virtual Machine) so your device may not get bricked as continous mode can potenially be dangerous for your computer. +It is recommended to use a virtual machine for tasks that require high security measures to prevent any potential harm to the main computer's system and data. ## ⚠️ Limitations This experiment aims to showcase the potential of GPT-4 but comes with some limitations: From 56edfc64401feb8fbe3cdfd8f1e2e4269f2b1db5 Mon Sep 17 00:00:00 2001 From: onekum <55006697+onekum@users.noreply.github.com> Date: Sun, 9 Apr 2023 04:28:23 -0400 Subject: [PATCH 32/47] Tell AI about the `do_nothing` command --- scripts/data/prompt.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/data/prompt.txt b/scripts/data/prompt.txt index 77a449de..582cf5d3 100644 --- a/scripts/data/prompt.txt +++ b/scripts/data/prompt.txt @@ -24,6 +24,7 @@ COMMANDS: 18. Execute Python File: "execute_python_file", args: "file": "" 19. Task Complete (Shutdown): "task_complete", args: "reason": "" 20. Generate Image: "generate_image", args: "prompt": "" +21. Do Nothing; command name: "do_nothing", args: "" RESOURCES: From 93a92d92fc56ebe49758b2e8bb0e9f0eba2f6513 Mon Sep 17 00:00:00 2001 From: onekum <55006697+onekum@users.noreply.github.com> Date: Sun, 9 Apr 2023 04:28:36 -0400 Subject: [PATCH 33/47] make `do_nothing` a valid command --- scripts/commands.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/commands.py b/scripts/commands.py index ba538395..02f3baa8 100644 --- a/scripts/commands.py +++ b/scripts/commands.py @@ -106,6 +106,8 @@ def execute_command(command_name, arguments): return execute_python_file(arguments["file"]) elif command_name == "generate_image": return generate_image(arguments["prompt"]) + elif command_name == "do_nothing": + return "No action performed." elif command_name == "task_complete": shutdown() else: @@ -283,4 +285,4 @@ def delete_agent(key): result = agents.delete_agent(key) if not result: return f"Agent {key} does not exist." - return f"Agent {key} deleted." \ No newline at end of file + return f"Agent {key} deleted." From 54e228504f6c16fe2bc0772d9b29358f2420adbd Mon Sep 17 00:00:00 2001 From: Weltolk <40228052+Weltolk@users.noreply.github.com> Date: Sun, 9 Apr 2023 18:05:50 +0800 Subject: [PATCH 34/47] Fix Elevenlabs link error --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ba80818d..66c95eca 100644 --- a/README.md +++ b/README.md @@ -92,7 +92,7 @@ pip install -r requirements.txt 4. Rename `.env.template` to `.env` and fill in your `OPENAI_API_KEY`. If you plan to use Speech Mode, fill in your `ELEVEN_LABS_API_KEY` as well. - Obtain your OpenAI API key from: https://platform.openai.com/account/api-keys. - - Obtain your ElevenLabs API key from: https://elevenlabs.io. You can view your xi-api-key using the "Profile" tab on the website. + - Obtain your ElevenLabs API key from: https://beta.elevenlabs.io. You can view your xi-api-key using the "Profile" tab on the website. - If you want to use GPT on an Azure instance, set `USE_AZURE` to `True` and provide the `OPENAI_API_BASE`, `OPENAI_API_VERSION` and `OPENAI_DEPLOYMENT_ID` values as explained here: https://pypi.org/project/openai/ in the `Microsoft Azure Endpoints` section ## 🔧 Usage From 011699e6a1df18254d08f6ec0b0d6b482e02e690 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Sun, 9 Apr 2023 15:39:11 +0200 Subject: [PATCH 35/47] Code review changes --- .gitignore | 2 +- Dockerfile | 2 +- scripts/ai_config.py | 2 -- scripts/ai_functions.py | 6 +++--- scripts/browse.py | 12 +++--------- scripts/call_ai_function.py | 4 ++-- scripts/chat.py | 14 ++++---------- scripts/commands.py | 4 ++-- scripts/config.py | 12 +----------- scripts/data.py | 4 ++-- scripts/execute_code.py | 6 +++--- scripts/file_operations.py | 2 -- scripts/json_parser.py | 6 ------ scripts/llm_utils.py | 1 - scripts/main.py | 36 ++++-------------------------------- scripts/speak.py | 4 ++-- scripts/spinner.py | 3 --- scripts/token_counter.py | 6 ------ 18 files changed, 28 insertions(+), 98 deletions(-) diff --git a/.gitignore b/.gitignore index fc20dfd2..6b8f00b5 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,4 @@ scripts/auto_gpt_workspace/* *.mpeg .env last_run_ai_settings.yaml -outputs/* +outputs/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index d2a127c7..c67291c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,7 +2,7 @@ FROM python:3.11 WORKDIR /app COPY scripts/ /app -COPY requirements.txt /app/requirements.txt + RUN pip install -r requirements.txt CMD ["python", "main.py"] diff --git a/scripts/ai_config.py b/scripts/ai_config.py index 0b521b7d..de214463 100644 --- a/scripts/ai_config.py +++ b/scripts/ai_config.py @@ -13,7 +13,6 @@ class AIConfig: # Soon this will go in a folder where it remembers more stuff about the run(s) SAVE_FILE = "last_run_ai_settings.yaml" - @classmethod def load(cls, config_file=SAVE_FILE): """Load variables from yaml file if it exists, otherwise use defaults.""" @@ -29,7 +28,6 @@ class AIConfig: return cls(ai_name, ai_role, ai_goals) - def save(self, config_file=SAVE_FILE): """Save variables to yaml file.""" config = {"ai_name": self.ai_name, "ai_role": self.ai_role, "ai_goals": self.ai_goals} diff --git a/scripts/ai_functions.py b/scripts/ai_functions.py index eb6dbd93..f93d7ea6 100644 --- a/scripts/ai_functions.py +++ b/scripts/ai_functions.py @@ -1,5 +1,5 @@ from typing import List, Optional -from json import dumps +import json from config import Config from call_ai_function import call_ai_function from json_parser import fix_and_parse_json @@ -23,7 +23,7 @@ def improve_code(suggestions: List[str], code: str) -> str: function_string = ( "def generate_improved_code(suggestions: List[str], code: str) -> str:" ) - args = [dumps(suggestions), code] + args = [json.dumps(suggestions), code] description_string = """Improves the provided code based on the suggestions provided, making no other changes.""" result_string = call_ai_function(function_string, args, description_string) @@ -36,7 +36,7 @@ def write_tests(code: str, focus: List[str]) -> str: function_string = ( "def create_test_cases(code: str, focus: Optional[str] = None) -> str:" ) - args = [code, dumps(focus)] + args = [code, json.dumps(focus)] description_string = """Generates test cases for the existing code, focusing on specific areas if required.""" result_string = call_ai_function(function_string, args, description_string) diff --git a/scripts/browse.py b/scripts/browse.py index 3fde8d25..89e41af4 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -1,14 +1,13 @@ -from requests import get +import requests from bs4 import BeautifulSoup from config import Config from llm_utils import create_chat_completion cfg = Config() - def scrape_text(url): """Scrape text from a webpage""" - response = get(url) + response = requests.get(url) # Check if the response contains an HTTP error if response.status_code >= 400: @@ -30,26 +29,22 @@ def scrape_text(url): def extract_hyperlinks(soup): """Extract hyperlinks from a BeautifulSoup object""" hyperlinks = [] - for link in soup.find_all('a', href=True): hyperlinks.append((link.text, link['href'])) - return hyperlinks def format_hyperlinks(hyperlinks): """Format hyperlinks into a list of strings""" formatted_links = [] - for link_text, link_url in hyperlinks: formatted_links.append(f"{link_text} ({link_url})") - return formatted_links def scrape_links(url): """Scrape hyperlinks from a webpage""" - response = get(url) + response = requests.get(url) # Check if the response contains an HTTP error if response.status_code >= 400: @@ -72,7 +67,6 @@ def split_text(text, max_length=8192): current_chunk = [] for paragraph in paragraphs: - if current_length + len(paragraph) + 1 <= max_length: current_chunk.append(paragraph) current_length += len(paragraph) + 1 diff --git a/scripts/call_ai_function.py b/scripts/call_ai_function.py index 44f9fd8c..82cf1a76 100644 --- a/scripts/call_ai_function.py +++ b/scripts/call_ai_function.py @@ -1,8 +1,8 @@ from config import Config -from llm_utils import create_chat_completion + cfg = Config() - +from llm_utils import create_chat_completion # This is a magic function that can do anything with no-code. See # https://github.com/Torantulino/AI-Functions for more info. def call_ai_function(function, args, description, model=cfg.smart_llm_model): diff --git a/scripts/chat.py b/scripts/chat.py index 17e36879..89a03509 100644 --- a/scripts/chat.py +++ b/scripts/chat.py @@ -1,11 +1,11 @@ -from time import sleep +import time import openai from dotenv import load_dotenv from config import Config import token_counter from llm_utils import create_chat_completion -cfg = Config() +cfg = Config() def create_chat_message(role, content): """ @@ -48,10 +48,8 @@ def chat_with_ai( """ model = cfg.fast_llm_model # TODO: Change model from hardcode to argument # Reserve 1000 tokens for the response - if debug: - print(f"Token limit: {token_limit}") - + print(f"Token limit: {token_limit}") send_token_limit = token_limit - 1000 current_context = [ @@ -73,7 +71,6 @@ def chat_with_ai( 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 @@ -99,16 +96,13 @@ def chat_with_ai( print(f"Send Token Count: {current_tokens_used}") print(f"Tokens remaining for response: {tokens_remaining}") print("------------ CONTEXT SENT TO AI ---------------") - for message in current_context: # Skip printing the prompt - if message["role"] == "system" and message["content"] == prompt: continue print( f"{message['role'].capitalize()}: {message['content']}") print() - print("----------- END OF CONTEXT ----------------") # TODO: use a model defined elsewhere, so that model can contain temperature and other settings we care about @@ -130,4 +124,4 @@ def chat_with_ai( except openai.error.RateLimitError: # TODO: WHen we switch to langchain, this is built in print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...") - sleep(10) + time.sleep(10) diff --git a/scripts/commands.py b/scripts/commands.py index 134b3fd8..38912b48 100644 --- a/scripts/commands.py +++ b/scripts/commands.py @@ -1,7 +1,7 @@ import browse import json import memory as mem -from datetime import datetime +import datetime import agent_manager as agents import speak from config import Config @@ -110,7 +110,7 @@ def execute_command(command_name, arguments): def get_datetime(): """Return the current date and time""" return "Current date and time: " + \ - datetime.now().strftime("%Y-%m-%d %H:%M:%S") + datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") def google_search(query, num_results=8): diff --git a/scripts/config.py b/scripts/config.py index 0650535f..cf8fe7e7 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -44,52 +44,42 @@ class Config(metaclass=Singleton): # Initialize the OpenAI API client openai.api_key = self.openai_api_key - def set_continuous_mode(self, value: bool): """Set the continuous mode value.""" self.continuous_mode = value - def set_speak_mode(self, value: bool): """Set the speak mode value.""" self.speak_mode = value - def set_fast_llm_model(self, value: str): """Set the fast LLM model value.""" self.fast_llm_model = value - def set_smart_llm_model(self, value: str): """Set the smart LLM model value.""" self.smart_llm_model = value - def set_fast_token_limit(self, value: int): """Set the fast token limit value.""" self.fast_token_limit = value - def set_smart_token_limit(self, value: int): """Set the smart token limit value.""" self.smart_token_limit = value - def set_openai_api_key(self, value: str): """Set the OpenAI API key value.""" self.openai_api_key = value - def set_elevenlabs_api_key(self, value: str): """Set the ElevenLabs API key value.""" self.elevenlabs_api_key = value - def set_google_api_key(self, value: str): """Set the Google API key value.""" self.google_api_key = value - def set_custom_search_engine_id(self, value: str): """Set the custom search engine ID value.""" - self.custom_search_engine_id = value + self.custom_search_engine_id = value \ No newline at end of file diff --git a/scripts/data.py b/scripts/data.py index 7694329a..e0840dd9 100644 --- a/scripts/data.py +++ b/scripts/data.py @@ -1,4 +1,4 @@ -from os import path +import os from pathlib import Path SRC_DIR = Path(__file__).parent @@ -6,7 +6,7 @@ def load_prompt(): """Load the prompt from data/prompt.txt""" try: # get directory of this file: - file_dir = Path(path.dirname(path.realpath(__file__))) + file_dir = Path(os.path.dirname(os.path.realpath(__file__))) data_dir = file_dir / "data" prompt_file = data_dir / "prompt.txt" # Load the promt from data/prompt.txt diff --git a/scripts/execute_code.py b/scripts/execute_code.py index 22ae1047..f34469dd 100644 --- a/scripts/execute_code.py +++ b/scripts/execute_code.py @@ -1,5 +1,5 @@ import docker -from os import path +import os def execute_python_file(file): @@ -11,9 +11,9 @@ def execute_python_file(file): if not file.endswith(".py"): return "Error: Invalid file type. Only .py files are allowed." - file_path = path.join(workspace_folder, file) + file_path = os.path.join(workspace_folder, file) - if not path.isfile(file_path): + if not os.path.isfile(file_path): return f"Error: File '{file}' does not exist." try: diff --git a/scripts/file_operations.py b/scripts/file_operations.py index cb054616..d7596c91 100644 --- a/scripts/file_operations.py +++ b/scripts/file_operations.py @@ -36,10 +36,8 @@ def write_to_file(filename, text): try: filepath = safe_join(working_directory, filename) directory = os.path.dirname(filepath) - if not os.path.exists(directory): os.makedirs(directory) - with open(filepath, "w") as f: f.write(text) return "File written to successfully." diff --git a/scripts/json_parser.py b/scripts/json_parser.py index e827b753..e46161ac 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -1,7 +1,6 @@ import json from call_ai_function import call_ai_function from config import Config - cfg = Config() def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): @@ -38,18 +37,15 @@ def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): json_str = json_str[:last_brace_index+1] return json.loads(json_str) except Exception as e: - if try_to_fix_with_gpt: print(f"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 = fix_json(json_str, json_schema, False) - if ai_fixed_json != "failed": return json.loads(ai_fixed_json) else: print(f"Failed to fix ai output, telling the AI.") # This allows the AI to react to the error message, which usually results in it correcting its ways. return json_str - else: raise e @@ -63,11 +59,9 @@ def fix_json(json_str: str, schema: str, debug=False) -> str: # If it doesn't already start with a "`", add one: if not json_str.startswith("`"): json_str = "```json\n" + json_str + "\n```" - result_string = call_ai_function( function_string, args, description_string, model=cfg.fast_llm_model ) - if debug: print("------------ JSON FIX ATTEMPT ---------------") print(f"Original JSON: {json_str}") diff --git a/scripts/llm_utils.py b/scripts/llm_utils.py index 2981c8a0..c512e997 100644 --- a/scripts/llm_utils.py +++ b/scripts/llm_utils.py @@ -1,6 +1,5 @@ import openai from config import Config - cfg = Config() openai.api_key = cfg.openai_api_key diff --git a/scripts/main.py b/scripts/main.py index e89e57dc..9b938559 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -27,24 +27,17 @@ def print_to_console( max_typing_speed=0.01): """Prints text to the console with a typing effect""" global cfg - if speak_text and cfg.speak_mode: speak.say_text(f"{title}. {content}") - print(title_color + title + " " + Style.RESET_ALL, end="") - if content: - if isinstance(content, list): content = " ".join(content) words = content.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 @@ -88,7 +81,6 @@ def print_assistant_thoughts(assistant_reply): if assistant_thoughts_plan: print_to_console("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): @@ -96,7 +88,6 @@ def print_assistant_thoughts(assistant_reply): # Split the input_string using the newline character and dashes lines = assistant_thoughts_plan.split('\n') - for line in lines: line = line.lstrip("- ") print_to_console("- ", Fore.GREEN, line.strip()) @@ -131,13 +122,11 @@ def load_variables(config_file="config.yaml"): # Prompt the user for input if config file is missing or empty values if not ai_name: ai_name = input("Name your AI: ") - if ai_name == "": ai_name = "Entrepreneur-GPT" if not ai_role: ai_role = input(f"{ai_name} is: ") - if ai_role == "": ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth." @@ -146,20 +135,16 @@ def load_variables(config_file="config.yaml"): print("For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") print("Enter nothing to load defaults, enter nothing when finished.") ai_goals = [] - for i in range(5): ai_goal = input(f"Goal {i+1}: ") - if ai_goal == "": break ai_goals.append(ai_goal) - if len(ai_goals) == 0: ai_goals = ["Increase net worth", "Grow Twitter Account", "Develop and manage multiple businesses autonomously"] # Save variables to yaml file config = {"ai_name": ai_name, "ai_role": ai_role, "ai_goals": ai_goals} - with open(config_file, "w") as file: documents = yaml.dump(config, file) @@ -168,7 +153,6 @@ def load_variables(config_file="config.yaml"): # Construct full prompt full_prompt = f"You are {ai_name}, {ai_role}\n{prompt_start}\n\nGOALS:\n\n" - for i, goal in enumerate(ai_goals): full_prompt += f"{i+1}. {goal}\n" @@ -179,7 +163,6 @@ def load_variables(config_file="config.yaml"): def construct_prompt(): """Construct the prompt for the AI to respond to""" config = AIConfig.load() - if config.ai_name: print_to_console( f"Welcome back! ", @@ -190,8 +173,7 @@ def construct_prompt(): Name: {config.ai_name} Role: {config.ai_role} Goals: {config.ai_goals} - Continue (y/n): """) - + Continue (y/n): """) if should_continue.lower() == "n": config = AIConfig() @@ -221,10 +203,8 @@ def prompt_user(): print_to_console( "Name your AI: ", Fore.GREEN, - "For example, 'Entrepreneur-GPT'") - + "For example, 'Entrepreneur-GPT'") ai_name = input("AI Name: ") - if ai_name == "": ai_name = "Entrepreneur-GPT" @@ -240,7 +220,6 @@ def prompt_user(): Fore.GREEN, "For example, 'an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth.'") ai_role = input(f"{ai_name} is: ") - if ai_role == "": ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth." @@ -248,19 +227,14 @@ def prompt_user(): print_to_console( "Enter up to 5 goals for your AI: ", Fore.GREEN, - "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") - + "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") print("Enter nothing to load defaults, enter nothing when finished.", flush=True) ai_goals = [] - for i in range(5): ai_goal = input(f"{Fore.LIGHTBLUE_EX}Goal{Style.RESET_ALL} {i+1}: ") - if ai_goal == "": break - ai_goals.append(ai_goal) - if len(ai_goals) == 0: ai_goals = ["Increase net worth", "Grow Twitter Account", "Develop and manage multiple businesses autonomously"] @@ -268,7 +242,6 @@ def prompt_user(): config = AIConfig(ai_name, ai_role, ai_goals) return config - def parse_arguments(): """Parses the arguments passed to the script""" global cfg @@ -346,8 +319,7 @@ while True: f"Enter 'y' to authorise command or 'n' to exit program, or enter feedback for {ai_name}...", flush=True) while True: - console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL) - + console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL) if console_input.lower() == "y": user_input = "GENERATE NEXT COMMAND JSON" break diff --git a/scripts/speak.py b/scripts/speak.py index 9f32fac2..398eaf59 100644 --- a/scripts/speak.py +++ b/scripts/speak.py @@ -2,7 +2,6 @@ import os from playsound import playsound import requests from config import Config - cfg = Config() import gtts @@ -20,7 +19,8 @@ def eleven_labs_speech(text, voice_index=0): tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format( voice_id=voices[voice_index]) formatted_message = {"text": text} - response = requests.post(tts_url, headers=tts_headers, json=formatted_message) + response = requests.post( + tts_url, headers=tts_headers, json=formatted_message) if response.status_code == 200: with open("speech.mpeg", "wb") as f: diff --git a/scripts/spinner.py b/scripts/spinner.py index 2b79ae26..df39dbbd 100644 --- a/scripts/spinner.py +++ b/scripts/spinner.py @@ -14,7 +14,6 @@ class Spinner: self.running = False self.spinner_thread = None - def spin(self): """Spin the spinner""" while self.running: @@ -23,14 +22,12 @@ class Spinner: time.sleep(self.delay) sys.stdout.write('\b' * (len(self.message) + 2)) - def __enter__(self): """Start the spinner""" self.running = True self.spinner_thread = threading.Thread(target=self.spin) self.spinner_thread.start() - def __exit__(self, exc_type, exc_value, exc_traceback): """Stop the spinner""" self.running = False diff --git a/scripts/token_counter.py b/scripts/token_counter.py index fc5b9c51..a28a9868 100644 --- a/scripts/token_counter.py +++ b/scripts/token_counter.py @@ -1,7 +1,6 @@ import tiktoken from typing import List, Dict - def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5-turbo-0301") -> int: """ Returns the number of tokens used by a list of messages. @@ -18,7 +17,6 @@ def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5 except KeyError: print("Warning: model not found. Using cl100k_base encoding.") encoding = tiktoken.get_encoding("cl100k_base") - if model == "gpt-3.5-turbo": # !Node: 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") @@ -34,19 +32,15 @@ def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5 else: raise NotImplementedError(f"""num_tokens_from_messages() is not implemented for model {model}. 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. From d05146c6c5766a17d7b40b932d3de4056e142123 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Sun, 9 Apr 2023 15:48:17 +0200 Subject: [PATCH 36/47] Delete unrelated changes with PR --- scripts/ai_config.py | 3 +-- scripts/main.py | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/scripts/ai_config.py b/scripts/ai_config.py index 14b45887..43d88ec3 100644 --- a/scripts/ai_config.py +++ b/scripts/ai_config.py @@ -40,8 +40,7 @@ class AIConfig: 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.""" # Construct full prompt - full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n" - + full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n" for i, goal in enumerate(self.ai_goals): full_prompt += f"{i+1}. {goal}\n" diff --git a/scripts/main.py b/scripts/main.py index 0c2a94e0..0a2d2447 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -170,10 +170,10 @@ def construct_prompt(): f"Would you like me to return to being {config.ai_name}?", speak_text=True) should_continue = input(f"""Continue with the last settings? - Name: {config.ai_name} - Role: {config.ai_role} - Goals: {config.ai_goals} - Continue (y/n): """) +Name: {config.ai_name} +Role: {config.ai_role} +Goals: {config.ai_goals} +Continue (y/n): """) if should_continue.lower() == "n": config = AIConfig() From 9105a9c7f862c1dc6901ea12b1fff10e70bb4aad Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Sun, 9 Apr 2023 16:31:04 +0200 Subject: [PATCH 37/47] Remove whitespaces --- scripts/ai_config.py | 2 +- scripts/main.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/ai_config.py b/scripts/ai_config.py index 43d88ec3..59c75201 100644 --- a/scripts/ai_config.py +++ b/scripts/ai_config.py @@ -40,7 +40,7 @@ class AIConfig: 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.""" # Construct full prompt - full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n" + full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n" for i, goal in enumerate(self.ai_goals): full_prompt += f"{i+1}. {goal}\n" diff --git a/scripts/main.py b/scripts/main.py index 0a2d2447..851108a7 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -173,7 +173,7 @@ def construct_prompt(): Name: {config.ai_name} Role: {config.ai_role} Goals: {config.ai_goals} -Continue (y/n): """) +Continue (y/n): """) if should_continue.lower() == "n": config = AIConfig() From c4ab35275a2acc585533c044e9d1f168a54d78b3 Mon Sep 17 00:00:00 2001 From: Andres Caicedo Date: Sun, 9 Apr 2023 16:34:36 +0200 Subject: [PATCH 38/47] Remove whitespaces --- scripts/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/scripts/main.py b/scripts/main.py index 851108a7..6115cffe 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -203,7 +203,7 @@ def prompt_user(): print_to_console( "Name your AI: ", Fore.GREEN, - "For example, 'Entrepreneur-GPT'") + "For example, 'Entrepreneur-GPT'") ai_name = input("AI Name: ") if ai_name == "": ai_name = "Entrepreneur-GPT" @@ -227,7 +227,7 @@ def prompt_user(): print_to_console( "Enter up to 5 goals for your AI: ", Fore.GREEN, - "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") + "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'") print("Enter nothing to load defaults, enter nothing when finished.", flush=True) ai_goals = [] for i in range(5): @@ -328,7 +328,7 @@ while True: f"Enter 'y' to authorise command, 'y -N' to run N continuous commands, 'n' to exit program, or enter feedback for {ai_name}...", flush=True) while True: - console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL) + console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL) if console_input.lower() == "y": user_input = "GENERATE NEXT COMMAND JSON" break From 54101c79973ca5ca8ccd7e1ac59856cb282c57d8 Mon Sep 17 00:00:00 2001 From: vandervoortj <64353639+vandervoortj@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:49:19 -0400 Subject: [PATCH 39/47] Update .gitignore Ignore auto-get.json --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 7091a872..ce0c33f9 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,5 @@ auto_gpt_workspace/* *.mpeg .env outputs/* -ai_settings.yaml \ No newline at end of file +ai_settings.yaml +auto-gpt.json From b6444de25dadd44da46acbcd37f3c8358bc18d03 Mon Sep 17 00:00:00 2001 From: BillSchumacher <34168009+BillSchumacher@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:22:55 -0500 Subject: [PATCH 40/47] Fixes for common json errors, cleanup json_parser file. --- scripts/json_parser.py | 82 +++++++++++++++++--------- scripts/json_utils.py | 127 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 181 insertions(+), 28 deletions(-) create mode 100644 scripts/json_utils.py diff --git a/scripts/json_parser.py b/scripts/json_parser.py index c863ccdb..6a5f073f 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -1,11 +1,13 @@ import json +from typing import Any, Dict, Union from call_ai_function import call_ai_function from config import Config +from json_utils import correct_json + cfg = Config() -def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): - json_schema = """ - { +JSON_SCHEMA = """ +{ "command": { "name": "command name", "args":{ @@ -20,44 +22,68 @@ def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True): "criticism": "constructive self-criticism", "speak": "thoughts summary to say to user" } - } - """ +} +""" + +def fix_and_parse_json( + json_str: str, + try_to_fix_with_gpt: bool = True +) -> Union[str, Dict[Any, Any]]: try: json_str = json_str.replace('\t', '') return json.loads(json_str) - except Exception as e: - # 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 + except json.JSONDecodeError as _: # noqa: F841 + json_str = correct_json(json_str) try: - brace_index = json_str.index("{") - json_str = json_str[brace_index:] - last_brace_index = json_str.rindex("}") - json_str = json_str[:last_brace_index+1] - return json.loads(json_str) - except Exception as e: - if try_to_fix_with_gpt: - print(f"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.") + return json.loads(json_str) + except json.JSONDecodeError as _: # noqa: F841 + pass + # 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_str.index("{") + json_str = json_str[brace_index:] + last_brace_index = json_str.rindex("}") + json_str = json_str[:last_brace_index+1] + return json.loads(json_str) + except json.JSONDecodeError as e: # noqa: F841 + if try_to_fix_with_gpt: + print("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 = fix_json(json_str, json_schema, cfg.debug) + ai_fixed_json = fix_json(json_str, JSON_SCHEMA, cfg.debug) if ai_fixed_json != "failed": - return json.loads(ai_fixed_json) + return json.loads(ai_fixed_json) else: - print(f"Failed to fix ai output, telling the AI.") # This allows the AI to react to the error message, which usually results in it correcting its ways. - return json_str - else: + # This allows the AI to react to the error message, + # which usually results in it correcting its ways. + print("Failed to fix ai output, telling the AI.") + return json_str + else: raise e - + + def fix_json(json_str: str, schema: str, debug=False) -> str: # Try to fix the JSON using gpt: function_string = "def fix_json(json_str: str, schema:str=None) -> str:" args = [f"'''{json_str}'''", f"'''{schema}'''"] - description_string = """Fixes the provided JSON string to make it parseable and fully complient with the provided schema.\n If an object or field specifed in the schema isn't contained within the correct JSON, it is ommited.\n This function is brilliant at guessing when the format is incorrect.""" + description_string = "Fixes the provided JSON string to make it parseable"\ + " and fully complient with the provided schema.\n If an object or"\ + " field specifed in the schema isn't contained within the correct"\ + " JSON, it is ommited.\n This function is brilliant at guessing"\ + " when the format is incorrect." # If it doesn't already start with a "`", add one: if not json_str.startswith("`"): - json_str = "```json\n" + json_str + "\n```" + json_str = "```json\n" + json_str + "\n```" result_string = call_ai_function( function_string, args, description_string, model=cfg.fast_llm_model ) @@ -68,11 +94,11 @@ def fix_json(json_str: str, schema: str, debug=False) -> str: print(f"Fixed JSON: {result_string}") print("----------- END OF FIX ATTEMPT ----------------") try: - json.loads(result_string) # just check the validity + json.loads(result_string) # just check the validity return result_string - except: + except: # noqa: E722 # Get the call stack: # import traceback # call_stack = traceback.format_exc() # print(f"Failed to fix JSON: '{json_str}' "+call_stack) - return "failed" \ No newline at end of file + return "failed" diff --git a/scripts/json_utils.py b/scripts/json_utils.py new file mode 100644 index 00000000..b3ffe4b9 --- /dev/null +++ b/scripts/json_utils.py @@ -0,0 +1,127 @@ +import re +import json +from config import Config + +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. + """ + import re + + 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 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): + return f'"{match.group(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 balance_braces(json_string: str) -> 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 + + try: + json.loads(json_string) + return json_string + except json.JSONDecodeError as e: + raise e + + +def fix_invalid_escape(json_str: str, error_message: str) -> str: + while error_message.startswith('Invalid \\escape'): + bad_escape_location = extract_char_position(error_message) + json_str = json_str[:bad_escape_location] + \ + json_str[bad_escape_location + 1:] + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + if cfg.debug: + print('json loads error - fix invalid escape', e) + error_message = str(e) + return json_str + + +def correct_json(json_str: str) -> str: + """ + Correct common JSON errors. + + Args: + json_str (str): The JSON string. + """ + + try: + if cfg.debug: + print("json", json_str) + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + if cfg.debug: + print('json loads error', e) + error_message = str(e) + if error_message.startswith('Invalid \\escape'): + json_str = fix_invalid_escape(json_str, error_message) + if error_message.startswith('Expecting property name enclosed in double quotes'): + json_str = add_quotes_to_property_names(json_str) + try: + json.loads(json_str) + return json_str + except json.JSONDecodeError as e: + if cfg.debug: + print('json loads error - add quotes', e) + error_message = str(e) + if balanced_str := balance_braces(json_str): + return balanced_str + return json_str From b0cb247b83d9b0b6c6f7d153ad0dfe076b6327ac Mon Sep 17 00:00:00 2001 From: Itamar Friedman Date: Mon, 10 Apr 2023 00:18:37 +0300 Subject: [PATCH 41/47] scrape_text: added tests + hande RequestException --- scripts/browse.py | 5 +- tests/test_browse_scrape_text.py | 102 +++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 tests/test_browse_scrape_text.py diff --git a/scripts/browse.py b/scripts/browse.py index 0fda3d7b..40e6ca1f 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -6,7 +6,10 @@ from llm_utils import create_chat_completion cfg = Config() def scrape_text(url): - response = requests.get(url, headers=cfg.user_agent_header) + try: + response = requests.get(url, headers=cfg.user_agent_header) + except requests.exceptions.RequestException as e: + return "Error: " + str(e) # Check if the response contains an HTTP error if response.status_code >= 400: diff --git a/tests/test_browse_scrape_text.py b/tests/test_browse_scrape_text.py new file mode 100644 index 00000000..1a08367e --- /dev/null +++ b/tests/test_browse_scrape_text.py @@ -0,0 +1,102 @@ + +# Generated by CodiumAI + +import requests +from unittest.mock import Mock +import pytest + +from scripts.browse import scrape_text + +""" +Code Analysis + +Objective: +The objective of the "scrape_text" function is to scrape the text content from a given URL and return it as a string, after removing any unwanted HTML tags and scripts. + +Inputs: +- url: a string representing the URL of the webpage to be scraped. + +Flow: +1. Send a GET request to the given URL using the requests library and the user agent header from the config file. +2. Check if the response contains an HTTP error. If it does, return an error message. +3. Use BeautifulSoup to parse the HTML content of the response and extract all script and style tags. +4. Get the text content of the remaining HTML using the get_text() method of BeautifulSoup. +5. Split the text into lines and then into chunks, removing any extra whitespace. +6. Join the chunks into a single string with newline characters between them. +7. Return the cleaned text. + +Outputs: +- A string representing the cleaned text content of the webpage. + +Additional aspects: +- The function uses the requests library and BeautifulSoup to handle the HTTP request and HTML parsing, respectively. +- The function removes script and style tags from the HTML to avoid including unwanted content in the text output. +- The function uses a generator expression to split the text into lines and chunks, which can improve performance for large amounts of text. +""" + + + +class TestScrapeText: + + # Tests that scrape_text() returns the expected text when given a valid URL. + def test_scrape_text_with_valid_url(self, mocker): + # Mock the requests.get() method to return a response with expected text + expected_text = "This is some sample text" + mock_response = mocker.Mock() + mock_response.status_code = 200 + mock_response.text = f"

{expected_text}

" + mocker.patch("requests.get", return_value=mock_response) + + # Call the function with a valid URL and assert that it returns the expected text + url = "http://www.example.com" + assert scrape_text(url) == expected_text + + # Tests that the function returns an error message when an invalid or unreachable url is provided. + def test_invalid_url(self, mocker): + # Mock the requests.get() method to raise an exception + mocker.patch("requests.get", side_effect=requests.exceptions.RequestException) + + # Call the function with an invalid URL and assert that it returns an error message + url = "http://www.invalidurl.com" + error_message = scrape_text(url) + assert "Error:" in error_message + + # Tests that the function returns an empty string when the html page contains no text to be scraped. + def test_no_text(self, mocker): + # Mock the requests.get() method to return a response with no text + mock_response = mocker.Mock() + mock_response.status_code = 200 + mock_response.text = "" + mocker.patch("requests.get", return_value=mock_response) + + # Call the function with a valid URL and assert that it returns an empty string + url = "http://www.example.com" + assert scrape_text(url) == "" + + # Tests that the function returns an error message when the response status code is an http error (>=400). + def test_http_error(self, mocker): + # Mock the requests.get() method to return a response with a 404 status code + mocker.patch('requests.get', return_value=Mock(status_code=404)) + + # Call the function with a URL + result = scrape_text("https://www.example.com") + + # Check that the function returns an error message + assert result == "Error: HTTP 404 error" + + # Tests that scrape_text() properly handles HTML tags. + def test_scrape_text_with_html_tags(self): + # Create a mock response object with HTML containing tags + html = "

This is bold text.

" + response = Mock() + response.status_code = 200 + response.text = html + + # Mock the requests.get() method to return the mock response object + requests.get = Mock(return_value=response) + + # Call the function with a URL + result = scrape_text("https://www.example.com") + + # Check that the function properly handles HTML tags + assert result == "This is bold text." \ No newline at end of file From ee1805c13641e576af4af7fb07f39adef5799625 Mon Sep 17 00:00:00 2001 From: Toran Bruce Richards Date: Mon, 10 Apr 2023 04:21:44 +0100 Subject: [PATCH 42/47] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 33 +++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index cb8ce34a..1ac8f864 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,18 +1,33 @@ -### Background + +Focus on a single, specific change. +Do not include any unrelated or "extra" modifications. +Provide clear documentation and explanations of the changes made. +Ensure diffs are limited to the intended lines — no applying preferred formatting styles or line endings (unless that's what the PR is about). +For guidance on committing only the specific lines you have changed, refer to this helpful video: https://youtu.be/8-hSNHHbiZg + +By following these guidelines, your PRs are more likely to be merged quickly after testing, as long as they align with the project's overall direction. --> + +### Background + ### Changes + - +### Documentation + ### Test Plan + - +### PR Quality Checklist +- [ ] My pull request is atomic and focuses on a single change. +- [ ] I have thouroughly tested my changes with multiple different prompts. +- [ ] I have considered potential risks and mitigations for my changes. +- [ ] I have documented my changes clearly and comprehensively. +- [ ] I have not snuck in any "extra" small tweaks changes -### Change Safety + -- [ ] I have added tests to cover my changes -- [ ] I have considered potential risks and mitigations for my changes - - + From 5727b052fe686506971f9d3472c07027116f2b5b Mon Sep 17 00:00:00 2001 From: Jason Drage Date: Mon, 10 Apr 2023 13:38:56 +1000 Subject: [PATCH 43/47] jd: ignore venv --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7091a872..f602018b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,6 @@ package-lock.json auto_gpt_workspace/* *.mpeg .env +venv/* outputs/* ai_settings.yaml \ No newline at end of file From 06f26cb29c6980c2c521780c83972fc09db819e4 Mon Sep 17 00:00:00 2001 From: Itamar Friedman Date: Mon, 10 Apr 2023 08:19:41 +0300 Subject: [PATCH 44/47] remove dependency of unittest, use pytest --- tests/test_browse_scrape_text.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/test_browse_scrape_text.py b/tests/test_browse_scrape_text.py index 1a08367e..27ebc0f6 100644 --- a/tests/test_browse_scrape_text.py +++ b/tests/test_browse_scrape_text.py @@ -2,7 +2,6 @@ # Generated by CodiumAI import requests -from unittest.mock import Mock import pytest from scripts.browse import scrape_text @@ -76,7 +75,7 @@ class TestScrapeText: # Tests that the function returns an error message when the response status code is an http error (>=400). def test_http_error(self, mocker): # Mock the requests.get() method to return a response with a 404 status code - mocker.patch('requests.get', return_value=Mock(status_code=404)) + mocker.patch('requests.get', return_value=mocker.Mock(status_code=404)) # Call the function with a URL result = scrape_text("https://www.example.com") @@ -85,15 +84,13 @@ class TestScrapeText: assert result == "Error: HTTP 404 error" # Tests that scrape_text() properly handles HTML tags. - def test_scrape_text_with_html_tags(self): + def test_scrape_text_with_html_tags(self, mocker): # Create a mock response object with HTML containing tags html = "

This is bold text.

" - response = Mock() - response.status_code = 200 - response.text = html - - # Mock the requests.get() method to return the mock response object - requests.get = Mock(return_value=response) + mock_response = mocker.Mock() + mock_response.status_code = 200 + mock_response.text = html + mocker.patch("requests.get", return_value=mock_response) # Call the function with a URL result = scrape_text("https://www.example.com") From da4a045bd6aa85805ff30493f7f0b00050a2dc80 Mon Sep 17 00:00:00 2001 From: Itamar Friedman Date: Mon, 10 Apr 2023 08:26:46 +0300 Subject: [PATCH 45/47] Adding most basic URL validation in scrape_text --- scripts/browse.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/browse.py b/scripts/browse.py index 40e6ca1f..7eeaaf4d 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -6,6 +6,10 @@ from llm_utils import create_chat_completion cfg = Config() def scrape_text(url): + # Most basic check if the URL is valid: + if not url.startswith('http'): + return "Error: Invalid URL" + try: response = requests.get(url, headers=cfg.user_agent_header) except requests.exceptions.RequestException as e: From b673302134a72ff8b5975ad62ecbfe4f8d3060e6 Mon Sep 17 00:00:00 2001 From: onekum <55006697+onekum@users.noreply.github.com> Date: Mon, 10 Apr 2023 04:43:34 -0400 Subject: [PATCH 46/47] Make `do_nothing` line uniform --- scripts/data/prompt.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/data/prompt.txt b/scripts/data/prompt.txt index 582cf5d3..ab281e81 100644 --- a/scripts/data/prompt.txt +++ b/scripts/data/prompt.txt @@ -24,7 +24,7 @@ COMMANDS: 18. Execute Python File: "execute_python_file", args: "file": "" 19. Task Complete (Shutdown): "task_complete", args: "reason": "" 20. Generate Image: "generate_image", args: "prompt": "" -21. Do Nothing; command name: "do_nothing", args: "" +21. Do Nothing: "do_nothing", args: "" RESOURCES: From 1f0209bba70c28717888cfa0c2f9f9e633264a7f Mon Sep 17 00:00:00 2001 From: Toran Bruce Richards Date: Mon, 10 Apr 2023 12:07:16 +0100 Subject: [PATCH 47/47] Fixe incorrect Docstring --- scripts/speak.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/speak.py b/scripts/speak.py index 8a3a64df..10dd7c07 100644 --- a/scripts/speak.py +++ b/scripts/speak.py @@ -15,7 +15,7 @@ tts_headers = { } def eleven_labs_speech(text, voice_index=0): - """Return the results of a google search""" + """Speak text using elevenlabs.io's API""" tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format( voice_id=voices[voice_index]) formatted_message = {"text": text}