diff --git a/.env.template b/.env.template index 6fbc8424..286303d7 100644 --- a/.env.template +++ b/.env.template @@ -12,6 +12,8 @@ USE_AZURE=False OPENAI_AZURE_API_BASE=your-base-url-for-azure OPENAI_AZURE_API_VERSION=api-version-for-azure OPENAI_AZURE_DEPLOYMENT_ID=deployment-id-for-azure +OPENAI_AZURE_CHAT_DEPLOYMENT_ID=deployment-id-for-azure-chat +OPENAI_AZURE_EMBEDDINGS_DEPLOYMENT_ID=deployment-id-for-azure-embeddigs IMAGE_PROVIDER=dalle HUGGINGFACE_API_TOKEN= USE_MAC_OS_TTS=False diff --git a/.gitignore b/.gitignore index 365b8ca5..aa0dceaa 100644 --- a/.gitignore +++ b/.gitignore @@ -7,10 +7,11 @@ package-lock.json auto_gpt_workspace/* *.mpeg .env -venv/* +*venv/* outputs/* ai_settings.yaml .vscode +.idea/* auto-gpt.json log.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 09a604eb..0529cbd9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ To contribute to this GitHub project, you can follow these steps: 2. Clone the repository to your local machine using the following command: ``` -git clone https://github.com/Torantulino/Auto-GPT +git clone https://github.com//Auto-GPT ``` 3. Create a new branch for your changes using the following command: diff --git a/README.md b/README.md index d2cbf981..5507a53c 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ Your support is greatly appreciated ## 📋 Requirements - [Python 3.8 or later](https://www.tutorialspoint.com/how-to-install-python-in-windows) -- OpenAI API key +- [OpenAI API key](https://platform.openai.com/account/api-keys) - [PINECONE API key](https://www.pinecone.io/) Optional: @@ -96,10 +96,9 @@ 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. -- If you want to use GPT on an Azure instance, set `USE_AZURE` to `True` and provide the `OPENAI_AZURE_API_BASE`, `OPENAI_AZURE_API_VERSION` and `OPENAI_AZURE_DEPLOYMENT_ID` values as explained here: https://pypi.org/project/openai/ in the `Microsoft Azure Endpoints` section + - 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. + - If you want to use GPT on an Azure instance, set `USE_AZURE` to `True` and provide the `OPENAI_AZURE_API_BASE`, `OPENAI_AZURE_API_VERSION` and `OPENAI_AZURE_DEPLOYMENT_ID` values as explained here: https://pypi.org/project/openai/ in the `Microsoft Azure Endpoints` section. Additionally you need separate deployments for both embeddings and chat. Add their ID values to `OPENAI_AZURE_CHAT_DEPLOYMENT_ID` and `OPENAI_AZURE_EMBEDDINGS_DEPLOYMENT_ID` respectively ## 🔧 Usage diff --git a/scripts/agent_manager.py b/scripts/agent_manager.py index e6bf3b86..a0e5f164 100644 --- a/scripts/agent_manager.py +++ b/scripts/agent_manager.py @@ -13,7 +13,7 @@ def create_agent(task, prompt, model): messages = [{"role": "user", "content": prompt}, ] - # Start GTP3 instance + # Start GPT instance agent_reply = create_chat_completion( model=model, messages=messages, @@ -41,7 +41,7 @@ def message_agent(key, message): # Add user message to message history before sending to agent messages.append({"role": "user", "content": message}) - # Start GTP3 instance + # Start GPT instance agent_reply = create_chat_completion( model=model, messages=messages, diff --git a/scripts/browse.py b/scripts/browse.py index b0c745ef..c3fc0662 100644 --- a/scripts/browse.py +++ b/scripts/browse.py @@ -2,9 +2,31 @@ import requests from bs4 import BeautifulSoup from config import Config from llm_utils import create_chat_completion +from urllib.parse import urlparse, urljoin cfg = Config() +# Function to check if the URL is valid +def is_valid_url(url): + try: + result = urlparse(url) + return all([result.scheme, result.netloc]) + except ValueError: + return False + +# Function to sanitize the URL +def sanitize_url(url): + return urljoin(url, urlparse(url).path) + +# Function to make a request with a specified timeout and handle exceptions +def make_request(url, timeout=10): + try: + response = requests.get(url, headers=cfg.user_agent_header, timeout=timeout) + response.raise_for_status() + return response + except requests.exceptions.RequestException as e: + return "Error: " + str(e) + # Define and check for local file address prefixes def check_local_file_access(url): local_prefixes = ['file:///', 'file://localhost', 'http://localhost', 'https://localhost'] @@ -12,7 +34,7 @@ def check_local_file_access(url): def scrape_text(url): """Scrape text from a webpage""" - # Most basic check if the URL is valid: + # Basic check if the URL is valid if not url.startswith('http'): return "Error: Invalid URL" @@ -20,14 +42,21 @@ def scrape_text(url): if check_local_file_access(url): return "Error: Access to local files is restricted" - try: - response = requests.get(url, headers=cfg.user_agent_header) - except requests.exceptions.RequestException as e: - return "Error: " + str(e) + # Validate the input URL + if not is_valid_url(url): + # Sanitize the input URL + sanitized_url = sanitize_url(url) - # Check if the response contains an HTTP error - if response.status_code >= 400: - return "Error: HTTP " + str(response.status_code) + " error" + # Make the request with a timeout and handle exceptions + response = make_request(sanitized_url) + + if isinstance(response, str): + return response + else: + # Sanitize the input URL + sanitized_url = sanitize_url(url) + + response = requests.get(sanitized_url, headers=cfg.user_agent_header) soup = BeautifulSoup(response.text, "html.parser") diff --git a/scripts/config.py b/scripts/config.py index a280e6cc..6e448954 100644 --- a/scripts/config.py +++ b/scripts/config.py @@ -49,6 +49,8 @@ class Config(metaclass=Singleton): self.openai_api_base = os.getenv("OPENAI_AZURE_API_BASE") self.openai_api_version = os.getenv("OPENAI_AZURE_API_VERSION") self.openai_deployment_id = os.getenv("OPENAI_AZURE_DEPLOYMENT_ID") + self.azure_chat_deployment_id = os.getenv("OPENAI_AZURE_CHAT_DEPLOYMENT_ID") + self.azure_embeddigs_deployment_id = os.getenv("OPENAI_AZURE_EMBEDDINGS_DEPLOYMENT_ID") openai.api_type = "azure" openai.api_base = self.openai_api_base openai.api_version = self.openai_api_version diff --git a/scripts/json_parser.py b/scripts/json_parser.py index e96c43f6..55244255 100644 --- a/scripts/json_parser.py +++ b/scripts/json_parser.py @@ -26,7 +26,7 @@ JSON_SCHEMA = """ """ -def fix_and_parse_json( +def fix_and_parse_json( json_str: str, try_to_fix_with_gpt: bool = True ) -> Union[str, Dict[Any, Any]]: @@ -35,8 +35,8 @@ def fix_and_parse_json( json_str = json_str.replace('\t', '') return json.loads(json_str) except json.JSONDecodeError as _: # noqa: F841 - json_str = correct_json(json_str) try: + json_str = correct_json(json_str) return json.loads(json_str) except json.JSONDecodeError as _: # noqa: F841 pass @@ -53,6 +53,7 @@ def fix_and_parse_json( last_brace_index = json_str.rindex("}") json_str = json_str[:last_brace_index+1] return json.loads(json_str) + # Can throw a ValueError if there is no "{" or "}" in the json_str except (json.JSONDecodeError, ValueError) as e: # noqa: F841 if try_to_fix_with_gpt: print("Warning: Failed to parse AI output, attempting to fix." diff --git a/scripts/llm_utils.py b/scripts/llm_utils.py index 94ba5f13..3fb348f0 100644 --- a/scripts/llm_utils.py +++ b/scripts/llm_utils.py @@ -9,7 +9,7 @@ def create_chat_completion(messages, model=None, temperature=None, max_tokens=No """Create a chat completion using the OpenAI API""" if cfg.use_azure: response = openai.ChatCompletion.create( - deployment_id=cfg.openai_deployment_id, + deployment_id=cfg.azure_chat_deployment_id, model=model, messages=messages, temperature=temperature, diff --git a/scripts/main.py b/scripts/main.py index d84e1508..15af0c38 100644 --- a/scripts/main.py +++ b/scripts/main.py @@ -266,6 +266,7 @@ def prompt_user(): def parse_arguments(): """Parses the arguments passed to the script""" global cfg + cfg.set_debug_mode(False) cfg.set_continuous_mode(False) cfg.set_speak_mode(False) @@ -274,6 +275,7 @@ def parse_arguments(): parser.add_argument('--speak', action='store_true', help='Enable Speak Mode') parser.add_argument('--debug', action='store_true', help='Enable Debug Mode') parser.add_argument('--gpt3only', action='store_true', help='Enable GPT3.5 Only Mode') + parser.add_argument('--gpt4only', action='store_true', help='Enable GPT4 Only Mode') args = parser.parse_args() if args.continuous: @@ -291,7 +293,14 @@ def parse_arguments(): if args.gpt3only: print_to_console("GPT3.5 Only Mode: ", Fore.GREEN, "ENABLED") cfg.set_smart_llm_model(cfg.fast_llm_model) + + if args.gpt4only: + print_to_console("GPT4 Only Mode: ", Fore.GREEN, "ENABLED") + cfg.set_fast_llm_model(cfg.smart_llm_model) + if args.debug: + print_to_console("Debug Mode: ", Fore.GREEN, "ENABLED") + cfg.set_debug_mode(True) # TODO: fill in llm values here @@ -383,7 +392,7 @@ while True: f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}") # Execute command - if command_name.lower().startswith( "error" ): + if command_name is not None and command_name.lower().startswith( "error" ): result = f"Command {command_name} threw the following error: " + arguments elif command_name == "human_feedback": result = f"Human feedback: {user_input}" diff --git a/scripts/memory/base.py b/scripts/memory/base.py index d7ab7fcf..bb22963a 100644 --- a/scripts/memory/base.py +++ b/scripts/memory/base.py @@ -1,12 +1,16 @@ """Base class for memory providers.""" import abc -from config import AbstractSingleton +from config import AbstractSingleton, Config import openai +cfg = Config() def get_ada_embedding(text): text = text.replace("\n", " ") - return openai.Embedding.create(input=[text], model="text-embedding-ada-002")["data"][0]["embedding"] + if cfg.use_azure: + return openai.Embedding.create(input=[text], engine=cfg.azure_embeddigs_deployment_id, model="text-embedding-ada-002")["data"][0]["embedding"] + else: + return openai.Embedding.create(input=[text], model="text-embedding-ada-002")["data"][0]["embedding"] class MemoryProviderSingleton(AbstractSingleton): diff --git a/scripts/memory/local.py b/scripts/memory/local.py index 8dc90021..372b59c4 100644 --- a/scripts/memory/local.py +++ b/scripts/memory/local.py @@ -54,8 +54,8 @@ class LocalCache(MemoryProviderSingleton): vector = vector[np.newaxis, :] self.data.embeddings = np.concatenate( [ - vector, self.data.embeddings, + vector, ], axis=0, ) diff --git a/tests/integration/memory_tests.py b/tests/integration/memory_tests.py new file mode 100644 index 00000000..ed444d91 --- /dev/null +++ b/tests/integration/memory_tests.py @@ -0,0 +1,49 @@ +import unittest +import random +import string +import sys +from pathlib import Path +# Add the parent directory of the 'scripts' folder to the Python path +sys.path.append(str(Path(__file__).resolve().parent.parent.parent / 'scripts')) +from config import Config +from memory.local import LocalCache + +class TestLocalCache(unittest.TestCase): + + def random_string(self, length): + return ''.join(random.choice(string.ascii_letters) for _ in range(length)) + + def setUp(self): + cfg = cfg = Config() + self.cache = LocalCache(cfg) + self.cache.clear() + + # Add example texts to the cache + self.example_texts = [ + 'The quick brown fox jumps over the lazy dog', + 'I love machine learning and natural language processing', + 'The cake is a lie, but the pie is always true', + 'ChatGPT is an advanced AI model for conversation' + ] + + for text in self.example_texts: + self.cache.add(text) + + # Add some random strings to test noise + for _ in range(5): + self.cache.add(self.random_string(10)) + + def test_get_relevant(self): + query = "I'm interested in artificial intelligence and NLP" + k = 3 + relevant_texts = self.cache.get_relevant(query, k) + + print(f"Top {k} relevant texts for the query '{query}':") + for i, text in enumerate(relevant_texts, start=1): + print(f"{i}. {text}") + + self.assertEqual(len(relevant_texts), k) + self.assertIn(self.example_texts[1], relevant_texts) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/json_tests.py b/tests/unit/json_tests.py new file mode 100644 index 00000000..fdac9c2f --- /dev/null +++ b/tests/unit/json_tests.py @@ -0,0 +1,115 @@ +import unittest +import os +import sys +# Probably a better way: +sys.path.append(os.path.abspath('../scripts')) +from json_parser import fix_and_parse_json + +class TestParseJson(unittest.TestCase): + def test_valid_json(self): + # Test that a valid JSON string is parsed correctly + json_str = '{"name": "John", "age": 30, "city": "New York"}' + obj = fix_and_parse_json(json_str) + self.assertEqual(obj, {"name": "John", "age": 30, "city": "New York"}) + + def test_invalid_json_minor(self): + # Test that an invalid JSON string can be fixed with gpt + json_str = '{"name": "John", "age": 30, "city": "New York",}' + self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), {"name": "John", "age": 30, "city": "New York"}) + + def test_invalid_json_major_with_gpt(self): + # Test that an invalid JSON string raises an error when try_to_fix_with_gpt is False + json_str = 'BEGIN: "name": "John" - "age": 30 - "city": "New York" :END' + self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=True), {"name": "John", "age": 30, "city": "New York"}) + + def test_invalid_json_major_without_gpt(self): + # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False + json_str = 'BEGIN: "name": "John" - "age": 30 - "city": "New York" :END' + # Assert that this raises an exception: + with self.assertRaises(Exception): + fix_and_parse_json(json_str, try_to_fix_with_gpt=False) + + def test_invalid_json_leading_sentence_with_gpt(self): + # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False + json_str = """I suggest we start by browsing the repository to find any issues that we can fix. + +{ + "command": { + "name": "browse_website", + "args":{ + "url": "https://github.com/Torantulino/Auto-GPT" + } + }, + "thoughts": + { + "text": "I suggest we start browsing the repository to find any issues that we can fix.", + "reasoning": "Browsing the repository will give us an idea of the current state of the codebase and identify any issues that we can address to improve the repo.", + "plan": "- Look through the repository to find any issues.\n- Investigate any issues to determine what needs to be fixed\n- Identify possible solutions to fix the issues\n- Open Pull Requests with fixes", + "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.", + "speak": "I will start browsing the repository to find any issues we can fix." + } +}""" + good_obj = { + "command": { + "name": "browse_website", + "args":{ + "url": "https://github.com/Torantulino/Auto-GPT" + } + }, + "thoughts": + { + "text": "I suggest we start browsing the repository to find any issues that we can fix.", + "reasoning": "Browsing the repository will give us an idea of the current state of the codebase and identify any issues that we can address to improve the repo.", + "plan": "- Look through the repository to find any issues.\n- Investigate any issues to determine what needs to be fixed\n- Identify possible solutions to fix the issues\n- Open Pull Requests with fixes", + "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.", + "speak": "I will start browsing the repository to find any issues we can fix." + } + } + # Assert that this raises an exception: + self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) + + + + def test_invalid_json_leading_sentence_with_gpt(self): + # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False + json_str = """I will first need to browse the repository (https://github.com/Torantulino/Auto-GPT) and identify any potential bugs that need fixing. I will use the "browse_website" command for this. + +{ + "command": { + "name": "browse_website", + "args":{ + "url": "https://github.com/Torantulino/Auto-GPT" + } + }, + "thoughts": + { + "text": "Browsing the repository to identify potential bugs", + "reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.", + "plan": "- Analyze the repository for potential bugs and areas of improvement", + "criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.", + "speak": "I am browsing the repository to identify potential bugs." + } +}""" + good_obj = { + "command": { + "name": "browse_website", + "args":{ + "url": "https://github.com/Torantulino/Auto-GPT" + } + }, + "thoughts": + { + "text": "Browsing the repository to identify potential bugs", + "reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.", + "plan": "- Analyze the repository for potential bugs and areas of improvement", + "criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.", + "speak": "I am browsing the repository to identify potential bugs." + } +} + # Assert that this raises an exception: + self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) + + + +if __name__ == '__main__': + unittest.main()