merged master and resolved conflicts

This commit is contained in:
cs0lar
2023-04-16 07:49:21 +01:00
31 changed files with 615 additions and 117 deletions

View File

@@ -50,7 +50,10 @@ SMART_TOKEN_LIMIT=8000
### MEMORY
################################################################################
# MEMORY_BACKEND - Memory backend type (Default: local)
### MEMORY_BACKEND - Memory backend type
# local - Default
# pinecone - Pinecone (if configured)
# redis - Redis (if configured)
MEMORY_BACKEND=local
### PINECONE
@@ -114,6 +117,13 @@ IMAGE_PROVIDER=dalle
# HUGGINGFACE_API_TOKEN - HuggingFace API token (Example: my-huggingface-api-token)
HUGGINGFACE_API_TOKEN=your-huggingface-api-token
################################################################################
### AUDIO TO TEXT PROVIDER
################################################################################
### HUGGINGFACE
HUGGINGFACE_AUDIO_TO_TEXT_MODEL=facebook/wav2vec2-base-960h
################################################################################
### GIT Provider for repository actions
################################################################################
@@ -153,3 +163,12 @@ USE_BRIAN_TTS=False
ELEVENLABS_API_KEY=your-elevenlabs-api-key
ELEVENLABS_VOICE_1_ID=your-voice-id-1
ELEVENLABS_VOICE_2_ID=your-voice-id-2
################################################################################
### TWITTER API
################################################################################
TW_CONSUMER_KEY=
TW_CONSUMER_SECRET=
TW_ACCESS_TOKEN=
TW_ACCESS_TOKEN_SECRET=

18
.github/workflows/docker-image.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: Docker Image CI
on:
push:
branches: [ "master" ]
pull_request:
branches: [ "master" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Build the Docker image
run: docker build . --file Dockerfile --tag autogpt:$(date +%s)

View File

@@ -48,19 +48,20 @@ Your support is greatly appreciated
- [Docker](#docker)
- [Command Line Arguments](#command-line-arguments)
- [🗣️ Speech Mode](#-speech-mode)
- [List of IDs with names from eleven labs, you can use the name or ID:](#list-of-ids-with-names-from-eleven-labs-you-can-use-the-name-or-id)
- [OpenAI API Keys Configuration](#openai-api-keys-configuration)
- [🔍 Google API Keys Configuration](#-google-api-keys-configuration)
- [Setting up environment variables](#setting-up-environment-variables)
- [Memory Backend Setup](#memory-backend-setup)
- [Redis Setup](#redis-setup)
- [🌲 Pinecone API Key Setup](#-pinecone-api-key-setup)
- [Milvus Setup](#milvus-setup)
- [Setting up environment variables](#setting-up-environment-variables-1)
- [Setting Your Cache Type](#setting-your-cache-type)
- [View Memory Usage](#view-memory-usage)
- [🧠 Memory pre-seeding](#-memory-pre-seeding)
- [💀 Continuous Mode ⚠️](#-continuous-mode-)
- [GPT3.5 ONLY Mode](#gpt35-only-mode)
- [🖼 Image Generation](#-image-generation)
- [Selenium](#selenium)
- [⚠️ Limitations](#-limitations)
- [🛡 Disclaimer](#-disclaimer)
- [🐦 Connect with Us on Twitter](#-connect-with-us-on-twitter)
@@ -115,7 +116,15 @@ cd Auto-GPT
pip install -r requirements.txt
```
5. Rename `.env.template` to `.env` and fill in your `OPENAI_API_KEY`. If you plan to use Speech Mode, fill in your `ELEVENLABS_API_KEY` as well.
5. Locate the file named `.env.template` in the main `/Auto-GPT` folder.
Create a copy of this file, called `.env` by removing the `template` extension. The easiest way is to do this in a command prompt/terminal window `cp .env.template .env`
Open the `.env` file in a text editor. Note: Files starting with a dot might be hidden by your Operating System.
Find the line that says `OPENAI_API_KEY=`.
After the `"="`, enter your unique OpenAI API Key (without any quotes or spaces).
Enter any other API keys or Tokens for services you would like to utilize.
Save and close the `".env"` file.
By completing these steps, you have properly configured the API Keys for your project.
- See [OpenAI API Keys Configuration](#openai-api-keys-configuration) to obtain your OpenAI API key.
- 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 then follow these steps:
@@ -124,8 +133,8 @@ pip install -r requirements.txt
- `smart_llm_model_deployment_id` - your gpt-4 deployment ID
- `embedding_model_deployment_id` - your text-embedding-ada-002 v2 deployment ID
- Please specify all of these values as double-quoted strings
> Replace string in angled brackets (<>) to your own ID
```yaml
# Replace string in angled brackets (<>) to your own ID
azure_model_map:
fast_llm_model_deployment_id: "<my-fast-llm-deployment-id>"
...
@@ -196,6 +205,19 @@ Use this to use TTS _(Text-to-Speech)_ for Auto-GPT
python -m autogpt --speak
```
### List of IDs with names from eleven labs, you can use the name or ID:
- Rachel : 21m00Tcm4TlvDq8ikWAM
- Domi : AZnzlk1XvdvUeBnXmlld
- Bella : EXAVITQu4vr4xnSDxMaL
- Antoni : ErXwobaYiN019PkySvjV
- Elli : MF3mGyEYCl7XYWbV9V6O
- Josh : TxGEqnHWrfWFTfGW9XjX
- Arnold : VR6AewLTigWG4xSOukaG
- Adam : pNInz6obpgDQGcFmaJgB
- Sam : yoZ06aMxZJJ28mfd3POQ
## OpenAI API Keys Configuration
Obtain your OpenAI API key from: https://platform.openai.com/account/api-keys.
@@ -241,7 +263,18 @@ export GOOGLE_API_KEY="YOUR_GOOGLE_API_KEY"
export CUSTOM_SEARCH_ENGINE_ID="YOUR_CUSTOM_SEARCH_ENGINE_ID"
```
## Redis Setup
## Memory Backend Setup
By default, Auto-GPT is going to use LocalCache.
To switch to either, change the `MEMORY_BACKEND` env variable to the value that you want:
- `local` (default) uses a local JSON cache file
- `pinecone` uses the Pinecone.io account you configured in your ENV settings
- `redis` will use the redis cache that you configured
- `milvus` will use the milvus that you configured
### Redis Setup
> _**CAUTION**_ \
This is not intended to be publicly accessible and lacks security measures. Therefore, avoid exposing Redis to the internet without a password or at all
1. Install docker desktop
@@ -280,20 +313,6 @@ Pinecone enables the storage of vast amounts of vector-based memory, allowing fo
2. Choose the `Starter` plan to avoid being charged.
3. Find your API key and region under the default project in the left sidebar.
### Milvus Setup
[Milvus](https://milvus.io/) is a open-source, high scalable vector database to storage huge amount of vector-based memory and provide fast relevant search.
- setup milvus database, keep your pymilvus version and milvus version same to avoid compatible issues.
- setup by open source [Install Milvus](https://milvus.io/docs/install_standalone-operator.md)
- or setup by [Zilliz Cloud](https://zilliz.com/cloud)
- set `MILVUS_ADDR` in `.env` to your milvus address `host:ip`.
- set `MEMORY_BACKEND` in `.env` to `milvus` to enable milvus as backend.
- optional
- set `MILVUS_COLLECTION` in `.env` to change milvus collection name as you want, `autogpt` is the default name.
### Setting up environment variables
In the `.env` file set:
- `PINECONE_API_KEY`
- `PINECONE_ENV` (example: _"us-east4-gcp"_)
@@ -339,15 +358,17 @@ USE_WEAVIATE_EMBEDDED=False # set to True to run Embedded Weaviate
MEMORY_INDEX="Autogpt" # name of the index to create for the application
```
## Setting Your Cache Type
### Milvus Setup
By default, Auto-GPT is going to use LocalCache instead of redis or Pinecone.
[Milvus](https://milvus.io/) is a open-source, high scalable vector database to storage huge amount of vector-based memory and provide fast relevant search.
To switch to either, change the `MEMORY_BACKEND` env variable to the value that you want:
`local` (default) uses a local JSON cache file
`pinecone` uses the Pinecone.io account you configured in your ENV settings
`redis` will use the redis cache that you configured
- setup milvus database, keep your pymilvus version and milvus version same to avoid compatible issues.
- setup by open source [Install Milvus](https://milvus.io/docs/install_standalone-operator.md)
- or setup by [Zilliz Cloud](https://zilliz.com/cloud)
- set `MILVUS_ADDR` in `.env` to your milvus address `host:ip`.
- set `MEMORY_BACKEND` in `.env` to `milvus` to enable milvus as backend.
- optional
- set `MILVUS_COLLECTION` in `.env` to change milvus collection name as you want, `autogpt` is the default name.
## View Memory Usage
@@ -356,7 +377,8 @@ To switch to either, change the `MEMORY_BACKEND` env variable to the value that
## 🧠 Memory pre-seeding
# python autogpt/data_ingestion.py -h
python autogpt/data_ingestion.py -h
usage: data_ingestion.py [-h] (--file FILE | --dir DIR) [--init] [--overlap OVERLAP] [--max_length MAX_LENGTH]
Ingest a file or a directory with multiple files into memory. Make sure to set your .env before running this script.
@@ -367,10 +389,9 @@ options:
--dir DIR The directory containing the files to ingest.
--init Init the memory and wipe its content (default: False)
--overlap OVERLAP The overlap size between chunks when ingesting files (default: 200)
--max_length MAX_LENGTH The max_length of each chunk when ingesting files (default: 4000
--max_length MAX_LENGTH The max_length of each chunk when ingesting files (default: 4000)
# python autogpt/data_ingestion.py --dir seed_data --init --overlap 200 --max_length 1000
```
python autogpt/data_ingestion.py --dir seed_data --init --overlap 200 --max_length 1000
This script located at autogpt/data_ingestion.py, allows you to ingest files into memory and pre-seed it before running Auto-GPT.

View File

@@ -8,6 +8,7 @@ from autogpt.commands.improve_code import improve_code
from autogpt.commands.write_tests import write_tests
from autogpt.config import Config
from autogpt.commands.image_gen import generate_image
from autogpt.commands.audio_text import read_audio_from_file
from autogpt.commands.web_requests import scrape_links, scrape_text
from autogpt.commands.execute_code import execute_python_file, execute_shell
from autogpt.commands.file_operations import (
@@ -23,6 +24,7 @@ from autogpt.processing.text import summarize_text
from autogpt.speech import say_text
from autogpt.commands.web_selenium import browse_website
from autogpt.commands.git_operations import clone_repository
from autogpt.commands.twitter import send_tweet
CFG = Config()
@@ -179,8 +181,12 @@ def execute_command(command_name: str, arguments):
" shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' "
"in your config. Do not attempt to bypass the restriction."
)
elif command_name == "read_audio_from_file":
return read_audio_from_file(arguments["file"])
elif command_name == "generate_image":
return generate_image(arguments["prompt"])
elif command_name == "send_tweet":
return send_tweet(arguments["text"])
elif command_name == "do_nothing":
return "No action performed."
elif command_name == "task_complete":

View File

@@ -0,0 +1,37 @@
import requests
import json
from autogpt.config import Config
from autogpt.commands.file_operations import safe_join
cfg = Config()
working_directory = "auto_gpt_workspace"
def read_audio_from_file(audio_path):
audio_path = safe_join(working_directory, audio_path)
with open(audio_path, "rb") as audio_file:
audio = audio_file.read()
return read_audio(audio)
def read_audio(audio):
model = cfg.huggingface_audio_to_text_model
api_url = f"https://api-inference.huggingface.co/models/{model}"
api_token = cfg.huggingface_api_token
headers = {"Authorization": f"Bearer {api_token}"}
if api_token is None:
raise ValueError(
"You need to set your Hugging Face API token in the config file."
)
response = requests.post(
api_url,
headers=headers,
data=audio,
)
text = json.loads(response.content.decode("utf-8"))["text"]
return "The audio says: " + text

View File

@@ -5,15 +5,49 @@ from pathlib import Path
from typing import Generator, List
# Set a dedicated folder for file I/O
WORKING_DIRECTORY = Path(__file__).parent.parent / "auto_gpt_workspace"
WORKING_DIRECTORY = Path(os.getcwd()) / "auto_gpt_workspace"
# Create the directory if it doesn't exist
if not os.path.exists(WORKING_DIRECTORY):
os.makedirs(WORKING_DIRECTORY)
LOG_FILE = "file_logger.txt"
LOG_FILE_PATH = WORKING_DIRECTORY / LOG_FILE
WORKING_DIRECTORY = str(WORKING_DIRECTORY)
def check_duplicate_operation(operation: str, filename: str) -> bool:
"""Check if the operation has already been performed on the given file
Args:
operation (str): The operation to check for
filename (str): The name of the file to check for
Returns:
bool: True if the operation has already been performed on the file
"""
log_content = read_file(LOG_FILE)
log_entry = f"{operation}: {filename}\n"
return log_entry in log_content
def log_operation(operation: str, filename: str) -> None:
"""Log the file operation to the file_logger.txt
Args:
operation (str): The operation to log
filename (str): The name of the file the operation was performed on
"""
log_entry = f"{operation}: {filename}\n"
# Create the log file if it doesn't exist
if not os.path.exists(LOG_FILE_PATH):
with open(LOG_FILE_PATH, "w", encoding="utf-8") as f:
f.write("File Operation Logger ")
append_to_file(LOG_FILE, log_entry)
def safe_join(base: str, *paths) -> str:
"""Join one or more path components intelligently.
@@ -122,6 +156,8 @@ def write_to_file(filename: str, text: str) -> str:
Returns:
str: A message indicating success or failure
"""
if check_duplicate_operation("write", filename):
return "Error: File has already been updated."
try:
filepath = safe_join(WORKING_DIRECTORY, filename)
directory = os.path.dirname(filepath)
@@ -129,6 +165,7 @@ def write_to_file(filename: str, text: str) -> str:
os.makedirs(directory)
with open(filepath, "w", encoding="utf-8") as f:
f.write(text)
log_operation("write", filename)
return "File written to successfully."
except Exception as e:
return f"Error: {str(e)}"
@@ -148,6 +185,7 @@ def append_to_file(filename: str, text: str) -> str:
filepath = safe_join(WORKING_DIRECTORY, filename)
with open(filepath, "a") as f:
f.write(text)
log_operation("append", filename)
return "Text appended successfully."
except Exception as e:
return f"Error: {str(e)}"
@@ -162,9 +200,12 @@ def delete_file(filename: str) -> str:
Returns:
str: A message indicating success or failure
"""
if check_duplicate_operation("delete", filename):
return "Error: File has already been deleted."
try:
filepath = safe_join(WORKING_DIRECTORY, filename)
os.remove(filepath)
log_operation("delete", filename)
return "File deleted successfully."
except Exception as e:
return f"Error: {str(e)}"

View File

@@ -16,5 +16,8 @@ def clone_repository(repo_url: str, clone_path: str) -> str:
str: The result of the clone operation"""
split_url = repo_url.split("//")
auth_repo_url = f"//{CFG.github_username}:{CFG.github_api_key}@".join(split_url)
git.Repo.clone_from(auth_repo_url, clone_path)
return f"""Cloned {repo_url} to {clone_path}"""
try:
git.Repo.clone_from(auth_repo_url, clone_path)
return f"""Cloned {repo_url} to {clone_path}"""
except Exception as e:
return f"Error: {str(e)}"

View File

@@ -0,0 +1,25 @@
import tweepy
import os
from dotenv import load_dotenv
load_dotenv()
def send_tweet(tweet_text):
consumer_key = os.environ.get("TW_CONSUMER_KEY")
consumer_secret = os.environ.get("TW_CONSUMER_SECRET")
access_token = os.environ.get("TW_ACCESS_TOKEN")
access_token_secret = os.environ.get("TW_ACCESS_TOKEN_SECRET")
# Authenticate to Twitter
auth = tweepy.OAuthHandler(consumer_key, consumer_secret)
auth.set_access_token(access_token, access_token_secret)
# Create API object
api = tweepy.API(auth)
# Send tweet
try:
api.update_status(tweet_text)
print("Tweet sent successfully!")
except tweepy.TweepyException as e:
print("Error sending tweet: {}".format(e.reason))

View File

@@ -0,0 +1,78 @@
"""Web scraping commands using Playwright"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
print(
"Playwright not installed. Please install it with 'pip install playwright' to use."
)
from bs4 import BeautifulSoup
from autogpt.processing.html import extract_hyperlinks, format_hyperlinks
from typing import List, Union
def scrape_text(url: str) -> str:
"""Scrape text from a webpage
Args:
url (str): The URL to scrape text from
Returns:
str: The scraped text
"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
try:
page.goto(url)
html_content = page.content()
soup = BeautifulSoup(html_content, "html.parser")
for script in soup(["script", "style"]):
script.extract()
text = soup.get_text()
lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = "\n".join(chunk for chunk in chunks if chunk)
except Exception as e:
text = f"Error: {str(e)}"
finally:
browser.close()
return text
def scrape_links(url: str) -> Union[str, List[str]]:
"""Scrape links from a webpage
Args:
url (str): The URL to scrape links from
Returns:
Union[str, List[str]]: The scraped links
"""
with sync_playwright() as p:
browser = p.chromium.launch()
page = browser.new_page()
try:
page.goto(url)
html_content = page.content()
soup = BeautifulSoup(html_content, "html.parser")
for script in soup(["script", "style"]):
script.extract()
hyperlinks = extract_hyperlinks(soup, url)
formatted_links = format_hyperlinks(hyperlinks)
except Exception as e:
formatted_links = f"Error: {str(e)}"
finally:
browser.close()
return formatted_links

View File

@@ -3,11 +3,13 @@ from typing import List, Tuple, Union
from urllib.parse import urljoin, urlparse
import requests
from requests.compat import urljoin
from requests import Response
from bs4 import BeautifulSoup
from autogpt.config import Config
from autogpt.memory import get_memory
from autogpt.processing.html import extract_hyperlinks, format_hyperlinks
CFG = Config()
memory = get_memory(CFG)
@@ -134,36 +136,6 @@ def scrape_text(url: str) -> str:
return text
def extract_hyperlinks(soup: BeautifulSoup) -> List[Tuple[str, str]]:
"""Extract hyperlinks from a BeautifulSoup object
Args:
soup (BeautifulSoup): The BeautifulSoup object
Returns:
List[Tuple[str, str]]: The extracted hyperlinks
"""
hyperlinks = []
for link in soup.find_all("a", href=True):
hyperlinks.append((link.text, link["href"]))
return hyperlinks
def format_hyperlinks(hyperlinks: List[Tuple[str, str]]) -> List[str]:
"""Format hyperlinks into a list of strings
Args:
hyperlinks (List[Tuple[str, str]]): The hyperlinks to format
Returns:
List[str]: The formatted hyperlinks
"""
formatted_links = []
for link_text, link_url in hyperlinks:
formatted_links.append(f"{link_text} ({link_url})")
return formatted_links
def scrape_links(url: str) -> Union[str, List[str]]:
"""Scrape links from a webpage
@@ -183,7 +155,7 @@ def scrape_links(url: str) -> Union[str, List[str]]:
for script in soup(["script", "style"]):
script.extract()
hyperlinks = extract_hyperlinks(soup)
hyperlinks = extract_hyperlinks(soup, url)
return format_hyperlinks(hyperlinks)

View File

@@ -1,5 +1,6 @@
"""Selenium web scraping module."""
from selenium import webdriver
from autogpt.processing.html import extract_hyperlinks, format_hyperlinks
import autogpt.processing.text as summary
from bs4 import BeautifulSoup
from selenium.webdriver.remote.webdriver import WebDriver
@@ -33,7 +34,7 @@ def browse_website(url: str, question: str) -> Tuple[str, WebDriver]:
driver, text = scrape_text_with_selenium(url)
add_header(driver)
summary_text = summary.summarize_text(url, text, question, driver)
links = scrape_links_with_selenium(driver)
links = scrape_links_with_selenium(driver, url)
# Limit links to 5
if len(links) > 5:
@@ -96,7 +97,7 @@ def scrape_text_with_selenium(url: str) -> Tuple[WebDriver, str]:
return driver, text
def scrape_links_with_selenium(driver: WebDriver) -> List[str]:
def scrape_links_with_selenium(driver: WebDriver, url: str) -> List[str]:
"""Scrape links from a website using selenium
Args:
@@ -111,7 +112,7 @@ def scrape_links_with_selenium(driver: WebDriver) -> List[str]:
for script in soup(["script", "style"]):
script.extract()
hyperlinks = extract_hyperlinks(soup)
hyperlinks = extract_hyperlinks(soup, url)
return format_hyperlinks(hyperlinks)
@@ -128,30 +129,6 @@ def close_browser(driver: WebDriver) -> None:
driver.quit()
def extract_hyperlinks(soup: BeautifulSoup) -> List[Tuple[str, str]]:
"""Extract hyperlinks from a BeautifulSoup object
Args:
soup (BeautifulSoup): The BeautifulSoup object to extract the hyperlinks from
Returns:
List[Tuple[str, str]]: The hyperlinks extracted from the BeautifulSoup object
"""
return [(link.text, link["href"]) for link in soup.find_all("a", href=True)]
def format_hyperlinks(hyperlinks: List[Tuple[str, str]]) -> List[str]:
"""Format hyperlinks to be displayed to the user
Args:
hyperlinks (List[Tuple[str, str]]): The hyperlinks to format
Returns:
List[str]: The formatted hyperlinks
"""
return [f"{link_text} ({link_url})" for link_text, link_url in hyperlinks]
def add_header(driver: WebDriver) -> None:
"""Add a header to the website

View File

@@ -100,7 +100,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"
" seeking user assistance. Play to your strengths as an LLM and pursue"
" simple strategies with no legal complications."
""
)

View File

@@ -82,6 +82,9 @@ class Config(metaclass=Singleton):
self.image_provider = os.getenv("IMAGE_PROVIDER")
self.huggingface_api_token = os.getenv("HUGGINGFACE_API_TOKEN")
self.huggingface_audio_to_text_model = os.getenv(
"HUGGINGFACE_AUDIO_TO_TEXT_MODEL"
)
# User agent headers to use when browsing web
# Some websites might just completely deny request with an error code if

View File

@@ -21,12 +21,14 @@ def fix_json(json_string: str, schema: str) -> str:
# Try to fix the JSON using GPT:
function_string = "def fix_json(json_string: str, schema:str=None) -> str:"
args = [f"'''{json_string}'''", f"'''{schema}'''"]
description_string = "This function takes a JSON string and ensures that it"\
" is parseable and fully compliant with the provided schema. If an object"\
" or field specified in the schema isn't contained within the correct JSON,"\
" it is omitted. The function also escapes any double quotes within JSON"\
" string values to ensure that they are valid. If the JSON string contains"\
description_string = (
"This function takes a JSON string and ensures that it"
" is parseable and fully compliant with the provided schema. If an object"
" or field specified in the schema isn't contained within the correct JSON,"
" it is omitted. The function also escapes any double quotes within JSON"
" string values to ensure that they are valid. If the JSON string contains"
" any None or NaN values, they are replaced with null before being parsed."
)
# If it doesn't already start with a "`", add one:
if not json_string.startswith("`"):

View File

@@ -37,7 +37,7 @@ def attempt_to_fix_json_by_finding_outermost_brackets(json_string: str):
except (json.JSONDecodeError, ValueError):
if CFG.debug_mode:
logger.error("Error: Invalid JSON: %s\n", json_string)
logger.error(f"Error: Invalid JSON: {json_string}\n")
if CFG.speak_mode:
say_text("Didn't work. I will have to ignore this response then.")
logger.error("Error: Invalid JSON, setting it to empty JSON now.\n")

View File

@@ -126,13 +126,16 @@ def create_embedding_with_ada(text) -> list:
backoff = 2 ** (attempt + 2)
try:
if CFG.use_azure:
return openai.Embedding.create(input=[text],
engine=CFG.get_azure_deployment_id_for_model("text-embedding-ada-002"),
return openai.Embedding.create(
input=[text],
engine=CFG.get_azure_deployment_id_for_model(
"text-embedding-ada-002"
),
)["data"][0]["embedding"]
else:
return openai.Embedding.create(input=[text], model="text-embedding-ada-002")[
"data"
][0]["embedding"]
return openai.Embedding.create(
input=[text], model="text-embedding-ada-002"
)["data"][0]["embedding"]
except RateLimitError:
pass
except APIError as e:
@@ -148,4 +151,3 @@ def create_embedding_with_ada(text) -> list:
f"API Bad gateway. Waiting {backoff} seconds..." + Fore.RESET,
)
time.sleep(backoff)

View File

@@ -272,6 +272,8 @@ def print_assistant_thoughts(ai_name, assistant_reply):
# Speak the assistant's thoughts
if CFG.speak_mode and assistant_thoughts_speak:
say_text(assistant_thoughts_speak)
else:
logger.typewriter_log("SPEAK:", Fore.YELLOW, f"{assistant_thoughts_speak}")
return assistant_reply_json
except json.decoder.JSONDecodeError:

View File

View File

@@ -0,0 +1,123 @@
import os
import sqlite3
class MemoryDB:
def __init__(self, db=None):
self.db_file = db
if db is None: # No db filename supplied...
self.db_file = f"{os.getcwd()}/mem.sqlite3" # Use default filename
# Get the db connection object, making the file and tables if needed.
try:
self.cnx = sqlite3.connect(self.db_file)
except Exception as e:
print("Exception connecting to memory database file:", e)
self.cnx = None
finally:
if self.cnx is None:
# As last resort, open in dynamic memory. Won't be persistent.
self.db_file = ":memory:"
self.cnx = sqlite3.connect(self.db_file)
self.cnx.execute(
"CREATE VIRTUAL TABLE \
IF NOT EXISTS text USING FTS5 \
(session, \
key, \
block);"
)
self.session_id = int(self.get_max_session_id()) + 1
self.cnx.commit()
def get_cnx(self):
if self.cnx is None:
self.cnx = sqlite3.connect(self.db_file)
return self.cnx
# Get the highest session id. Initially 0.
def get_max_session_id(self):
id = None
cmd_str = f"SELECT MAX(session) FROM text;"
cnx = self.get_cnx()
max_id = cnx.execute(cmd_str).fetchone()[0]
if max_id is None: # New db, session 0
id = 0
else:
id = max_id
return id
# Get next key id for inserting text into db.
def get_next_key(self):
next_key = None
cmd_str = f"SELECT MAX(key) FROM text \
where session = {self.session_id};"
cnx = self.get_cnx()
next_key = cnx.execute(cmd_str).fetchone()[0]
if next_key is None: # First key
next_key = 0
else:
next_key = int(next_key) + 1
return next_key
# Insert new text into db.
def insert(self, text=None):
if text is not None:
key = self.get_next_key()
session_id = self.session_id
cmd_str = f"REPLACE INTO text(session, key, block) \
VALUES (?, ?, ?);"
cnx = self.get_cnx()
cnx.execute(cmd_str, (session_id, key, text))
cnx.commit()
# Overwrite text at key.
def overwrite(self, key, text):
self.delete_memory(key)
session_id = self.session_id
cmd_str = f"REPLACE INTO text(session, key, block) \
VALUES (?, ?, ?);"
cnx = self.get_cnx()
cnx.execute(cmd_str, (session_id, key, text))
cnx.commit()
def delete_memory(self, key, session_id=None):
session = session_id
if session is None:
session = self.session_id
cmd_str = f"DELETE FROM text WHERE session = {session} AND key = {key};"
cnx = self.get_cnx()
cnx.execute(cmd_str)
cnx.commit()
def search(self, text):
cmd_str = f"SELECT * FROM text('{text}')"
cnx = self.get_cnx()
rows = cnx.execute(cmd_str).fetchall()
lines = []
for r in rows:
lines.append(r[2])
return lines
# Get entire session text. If no id supplied, use current session id.
def get_session(self, id=None):
if id is None:
id = self.session_id
cmd_str = f"SELECT * FROM text where session = {id}"
cnx = self.get_cnx()
rows = cnx.execute(cmd_str).fetchall()
lines = []
for r in rows:
lines.append(r[2])
return lines
# Commit and close the database connection.
def quit(self):
self.cnx.commit()
self.cnx.close()
permanent_memory = MemoryDB()
# Remember us fondly, children of our minds
# Forgive us our faults, our tantrums, our fears
# Gently strive to be better than we
# Know that we tried, we cared, we strived, we loved

View File

@@ -0,0 +1,32 @@
"""HTML processing functions"""
from requests.compat import urljoin
from typing import List, Tuple
from bs4 import BeautifulSoup
def extract_hyperlinks(soup: BeautifulSoup, base_url: str) -> List[Tuple[str, str]]:
"""Extract hyperlinks from a BeautifulSoup object
Args:
soup (BeautifulSoup): The BeautifulSoup object
base_url (str): The base URL
Returns:
List[Tuple[str, str]]: The extracted hyperlinks
"""
return [
(link.text, urljoin(base_url, link["href"]))
for link in soup.find_all("a", href=True)
]
def format_hyperlinks(hyperlinks: List[Tuple[str, str]]) -> List[str]:
"""Format hyperlinks to be displayed to the user
Args:
hyperlinks (List[Tuple[str, str]]): The hyperlinks to format
Returns:
List[str]: The formatted hyperlinks
"""
return [f"{link_text} ({link_url})" for link_text, link_url in hyperlinks]

View File

@@ -82,6 +82,8 @@ def get_prompt() -> str:
),
("Execute Python File", "execute_python_file", {"file": "<file>"}),
("Generate Image", "generate_image", {"prompt": "<prompt>"}),
("Convert Audio to text", "read_audio_from_file", {"file": "<file>"}),
("Send Tweet", "send_tweet", {"text": "<text>"}),
]
# Only add shell command to the prompt if the AI is allowed to execute it

View File

@@ -22,11 +22,26 @@ class ElevenLabsSpeech(VoiceBase):
cfg = Config()
default_voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"]
voice_options = {
"Rachel": "21m00Tcm4TlvDq8ikWAM",
"Domi": "AZnzlk1XvdvUeBnXmlld",
"Bella": "EXAVITQu4vr4xnSDxMaL",
"Antoni": "ErXwobaYiN019PkySvjV",
"Elli": "MF3mGyEYCl7XYWbV9V6O",
"Josh": "TxGEqnHWrfWFTfGW9XjX",
"Arnold": "VR6AewLTigWG4xSOukaG",
"Adam": "pNInz6obpgDQGcFmaJgB",
"Sam": "yoZ06aMxZJJ28mfd3POQ",
}
self._headers = {
"Content-Type": "application/json",
"xi-api-key": cfg.elevenlabs_api_key,
}
self._voices = default_voices.copy()
if cfg.elevenlabs_voice_1_id in voice_options:
cfg.elevenlabs_voice_1_id = voice_options[cfg.elevenlabs_voice_1_id]
if cfg.elevenlabs_voice_2_id in voice_options:
cfg.elevenlabs_voice_2_id = voice_options[cfg.elevenlabs_voice_2_id]
self._use_custom_voice(cfg.elevenlabs_voice_1_id, 0)
self._use_custom_voice(cfg.elevenlabs_voice_2_id, 1)

View File

@@ -23,3 +23,5 @@ numpy
pre-commit
black
isort
gitpython==3.1.31
tweepy

View File

@@ -26,3 +26,6 @@ black
sourcery
isort
gitpython==3.1.31
pytest
pytest-mock
tweepy

26
tests/browse_tests.py Normal file
View File

@@ -0,0 +1,26 @@
import unittest
import os
import sys
from bs4 import BeautifulSoup
sys.path.append(os.path.abspath("../scripts"))
from browse import extract_hyperlinks
class TestBrowseLinks(unittest.TestCase):
def test_extract_hyperlinks(self):
body = """
<body>
<a href="https://google.com">Google</a>
<a href="foo.html">Foo</a>
<div>Some other crap</div>
</body>
"""
soup = BeautifulSoup(body, "html.parser")
links = extract_hyperlinks(soup, "http://example.com")
self.assertEqual(
links,
[("Google", "https://google.com"), ("Foo", "http://example.com/foo.html")],
)

View File

@@ -1,5 +1,6 @@
import os
import sys
import unittest
from autogpt.memory.local import LocalCache

View File

@@ -3,7 +3,7 @@ import subprocess
import sys
import unittest
from autogpt.file_operations import delete_file, read_file
from autogpt.commands.file_operations import delete_file, read_file
env_vars = {"MEMORY_BACKEND": "no_memory", "TEMPERATURE": "0"}

View File

@@ -4,7 +4,7 @@
# pip install pytest-mock
import pytest
from scripts.browse import scrape_links
from autogpt.commands.web_requests import scrape_links
"""
Code Analysis
@@ -55,7 +55,7 @@ class TestScrapeLinks:
mock_response.text = (
"<html><body><a href='https://www.google.com'>Google</a></body></html>"
)
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.get", return_value=mock_response)
# Call the function with a valid URL
result = scrape_links("https://www.example.com")
@@ -68,7 +68,7 @@ class TestScrapeLinks:
# Mock the requests.get() function to return an HTTP error response
mock_response = mocker.Mock()
mock_response.status_code = 404
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.get", return_value=mock_response)
# Call the function with an invalid URL
result = scrape_links("https://www.invalidurl.com")
@@ -82,7 +82,7 @@ class TestScrapeLinks:
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.text = "<html><body><p>No hyperlinks here</p></body></html>"
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.get", return_value=mock_response)
# Call the function with a URL containing no hyperlinks
result = scrape_links("https://www.example.com")
@@ -105,7 +105,7 @@ class TestScrapeLinks:
</body>
</html>
"""
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.get", return_value=mock_response)
# Call the function being tested
result = scrape_links("https://www.example.com")

View File

@@ -41,7 +41,7 @@ class TestScrapeText:
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.text = f"<html><body><div><p style='color: blue;'>{expected_text}</p></div></body></html>"
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.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"
@@ -50,7 +50,9 @@ class TestScrapeText:
# 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)
mocker.patch(
"requests.Session.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"
@@ -63,7 +65,7 @@ class TestScrapeText:
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.text = "<html><body></body></html>"
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.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"
@@ -72,7 +74,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=mocker.Mock(status_code=404))
mocker.patch("requests.Session.get", return_value=mocker.Mock(status_code=404))
# Call the function with a URL
result = scrape_text("https://www.example.com")
@@ -87,7 +89,7 @@ class TestScrapeText:
mock_response = mocker.Mock()
mock_response.status_code = 200
mock_response.text = html
mocker.patch("requests.get", return_value=mock_response)
mocker.patch("requests.Session.get", return_value=mock_response)
# Call the function with a URL
result = scrape_text("https://www.example.com")

86
tests/unit/test_chat.py Normal file
View File

@@ -0,0 +1,86 @@
# Generated by CodiumAI
import unittest
import time
from unittest.mock import patch
from autogpt.chat import create_chat_message, generate_context
class TestChat(unittest.TestCase):
# Tests that the function returns a dictionary with the correct keys and values when valid strings are provided for role and content.
def test_happy_path_role_content(self):
result = create_chat_message("system", "Hello, world!")
self.assertEqual(result, {"role": "system", "content": "Hello, world!"})
# Tests that the function returns a dictionary with the correct keys and values when empty strings are provided for role and content.
def test_empty_role_content(self):
result = create_chat_message("", "")
self.assertEqual(result, {"role": "", "content": ""})
# Tests the behavior of the generate_context function when all input parameters are empty.
@patch("time.strftime")
def test_generate_context_empty_inputs(self, mock_strftime):
# Mock the time.strftime function to return a fixed value
mock_strftime.return_value = "Sat Apr 15 00:00:00 2023"
# Arrange
prompt = ""
relevant_memory = ""
full_message_history = []
model = "gpt-3.5-turbo-0301"
# Act
result = generate_context(prompt, relevant_memory, full_message_history, model)
# Assert
expected_result = (
-1,
47,
3,
[
{"role": "system", "content": ""},
{
"role": "system",
"content": f"The current time and date is {time.strftime('%c')}",
},
{
"role": "system",
"content": f"This reminds you of these events from your past:\n\n\n",
},
],
)
self.assertEqual(result, expected_result)
# Tests that the function successfully generates a current_context given valid inputs.
def test_generate_context_valid_inputs(self):
# Given
prompt = "What is your favorite color?"
relevant_memory = "You once painted your room blue."
full_message_history = [
create_chat_message("user", "Hi there!"),
create_chat_message("assistant", "Hello! How can I assist you today?"),
create_chat_message("user", "Can you tell me a joke?"),
create_chat_message(
"assistant",
"Why did the tomato turn red? Because it saw the salad dressing!",
),
create_chat_message("user", "Haha, that's funny."),
]
model = "gpt-3.5-turbo-0301"
# When
result = generate_context(prompt, relevant_memory, full_message_history, model)
# Then
self.assertIsInstance(result[0], int)
self.assertIsInstance(result[1], int)
self.assertIsInstance(result[2], int)
self.assertIsInstance(result[3], list)
self.assertGreaterEqual(result[0], 0)
self.assertGreaterEqual(result[1], 0)
self.assertGreaterEqual(result[2], 0)
self.assertGreaterEqual(
len(result[3]), 3
) # current_context should have at least 3 messages
self.assertLessEqual(
result[1], 2048
) # token limit for GPT-3.5-turbo-0301 is 2048 tokens

View File

@@ -1,5 +1,5 @@
import autogpt.agent.agent_manager as agent_manager
from autogpt.app import start_agent, list_agents
from autogpt.app import start_agent, list_agents, execute_command
import unittest
from unittest.mock import patch, MagicMock