merged master and resolved conflicts

This commit is contained in:
cs0lar
2023-04-15 06:51:04 +01:00
69 changed files with 3219 additions and 1376 deletions

23
.devcontainer/Dockerfile Normal file
View File

@@ -0,0 +1,23 @@
# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster
ARG VARIANT=3-bullseye
FROM python:3.8
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# Remove imagemagick due to https://security-tracker.debian.org/tracker/CVE-2019-10131
&& apt-get purge -y imagemagick imagemagick-6-common
# Temporary: Upgrade python packages due to https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-40897
# They are installed by the base image (python) which does not have the patch.
RUN python3 -m pip install --upgrade setuptools
# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image.
# COPY requirements.txt /tmp/pip-tmp/
# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \
# && rm -rf /tmp/pip-tmp
# [Optional] Uncomment this section to install additional OS packages.
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
# && apt-get -y install --no-install-recommends <your-package-list-here>
# [Optional] Uncomment this line to install global node packages.
# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g <your-package-here>" 2>&1

View File

@@ -0,0 +1,39 @@
{
"build": {
"dockerfile": "./Dockerfile",
"context": "."
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
"installZsh": "true",
"username": "vscode",
"userUid": "1000",
"userGid": "1000",
"upgradePackages": "true"
},
"ghcr.io/devcontainers/features/python:1": "none",
"ghcr.io/devcontainers/features/node:1": "none",
"ghcr.io/devcontainers/features/git:1": {
"version": "latest",
"ppa": "false"
}
},
// Configure tool-specific properties.
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python"
}
}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "pip3 install --user -r requirements.txt",
// Set `remoteUser` to `root` to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@@ -1,19 +1,94 @@
PINECONE_API_KEY=your-pinecone-api-key ################################################################################
PINECONE_ENV=your-pinecone-region ### AUTO-GPT - GENERAL SETTINGS
################################################################################
# EXECUTE_LOCAL_COMMANDS - Allow local command execution (Example: False)
EXECUTE_LOCAL_COMMANDS=False
# BROWSE_CHUNK_MAX_LENGTH - When browsing website, define the length of chunk stored in memory
BROWSE_CHUNK_MAX_LENGTH=8192
# BROWSE_SUMMARY_MAX_TOKEN - Define the maximum length of the summary generated by GPT agent when browsing website
BROWSE_SUMMARY_MAX_TOKEN=300
# USER_AGENT - Define the user-agent used by the requests library to browse website (string)
# USER_AGENT="Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"
# AI_SETTINGS_FILE - Specifies which AI Settings file to use (defaults to ai_settings.yaml)
AI_SETTINGS_FILE=ai_settings.yaml
################################################################################
### LLM PROVIDER
################################################################################
### OPENAI
# OPENAI_API_KEY - OpenAI API Key (Example: my-openai-api-key)
# TEMPERATURE - Sets temperature in OpenAI (Default: 1)
# USE_AZURE - Use Azure OpenAI or not (Default: False)
OPENAI_API_KEY=your-openai-api-key OPENAI_API_KEY=your-openai-api-key
TEMPERATURE=1 TEMPERATURE=1
ELEVENLABS_API_KEY=your-elevenlabs-api-key USE_AZURE=False
ELEVENLABS_VOICE_1_ID=your-voice-id
ELEVENLABS_VOICE_2_ID=your-voice-id ### AZURE
# OPENAI_AZURE_API_BASE - OpenAI API base URL for Azure (Example: https://my-azure-openai-url.com)
# OPENAI_AZURE_API_VERSION - OpenAI API version for Azure (Example: v1)
# OPENAI_AZURE_DEPLOYMENT_ID - OpenAI deployment ID for Azure (Example: my-deployment-id)
# OPENAI_AZURE_CHAT_DEPLOYMENT_ID - OpenAI deployment ID for Azure Chat (Example: my-deployment-id-for-azure-chat)
# OPENAI_AZURE_EMBEDDINGS_DEPLOYMENT_ID - OpenAI deployment ID for Embedding (Example: my-deployment-id-for-azure-embeddigs)
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
################################################################################
### LLM MODELS
################################################################################
# SMART_LLM_MODEL - Smart language model (Default: gpt-4)
# FAST_LLM_MODEL - Fast language model (Default: gpt-3.5-turbo)
SMART_LLM_MODEL=gpt-4 SMART_LLM_MODEL=gpt-4
FAST_LLM_MODEL=gpt-3.5-turbo FAST_LLM_MODEL=gpt-3.5-turbo
GOOGLE_API_KEY=
CUSTOM_SEARCH_ENGINE_ID= ### LLM MODEL SETTINGS
USE_AZURE=False # FAST_TOKEN_LIMIT - Fast token limit for OpenAI (Default: 4000)
EXECUTE_LOCAL_COMMANDS=False # SMART_TOKEN_LIMIT - Smart token limit for OpenAI (Default: 8000)
IMAGE_PROVIDER=dalle # When using --gpt3onlythis needs to be set to 4000.
HUGGINGFACE_API_TOKEN= FAST_TOKEN_LIMIT=4000
USE_MAC_OS_TTS=False SMART_TOKEN_LIMIT=8000
################################################################################
### MEMORY
################################################################################
# MEMORY_BACKEND - Memory backend type (Default: local)
>>>>>>> master
MEMORY_BACKEND=local
### PINECONE
# PINECONE_API_KEY - Pinecone API Key (Example: my-pinecone-api-key)
# PINECONE_ENV - Pinecone environment (region) (Example: us-west-2)
PINECONE_API_KEY=your-pinecone-api-key
PINECONE_ENV=your-pinecone-region
### REDIS
# REDIS_HOST - Redis host (Default: localhost)
# REDIS_PORT - Redis port (Default: 6379)
# REDIS_PASSWORD - Redis password (Default: "")
# WIPE_REDIS_ON_START - Wipes data / index on start (Default: False)
# MEMORY_INDEX - Name of index created in Redis database (Default: auto-gpt)
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
WIPE_REDIS_ON_START=False
MEMORY_INDEX=auto-gpt
### WEAVIATE
# MEMORY_BACKEND - Use 'weaviate' to use Weaviate vector storage
# WEAVIATE_HOST - Weaviate host IP
# WEAVIATE_PORT - Weaviate host port
# WEAVIATE_PROTOCOL - Weaviate host protocol (e.g. 'http')
# USE_WEAVIATE_EMBEDDED - Whether to use Embedded Weaviate
# WEAVIATE_EMBEDDED_PATH - File system path were to persist data when running Embedded Weaviate
# WEAVIATE_USERNAME - Weaviate username
# WEAVIATE_PASSWORD - Weaviate password
# WEAVIATE_API_KEY - Weaviate API key if using API-key-based authentication
# MEMORY_INDEX - Name of index to create in Weaviate
WEAVIATE_HOST="127.0.0.1" WEAVIATE_HOST="127.0.0.1"
WEAVIATE_PORT=8080 WEAVIATE_PORT=8080
WEAVIATE_PROTOCOL="http" WEAVIATE_PROTOCOL="http"
@@ -22,5 +97,49 @@ WEAVIATE_EMBEDDED_PATH="/home/me/.local/share/weaviate"
WEAVIATE_USERNAME= WEAVIATE_USERNAME=
WEAVIATE_PASSWORD= WEAVIATE_PASSWORD=
WEAVIATE_API_KEY= WEAVIATE_API_KEY=
MEMORY_INDEX="auto-gpt" MEMORY_INDEX=AutoGpt
MEMORY_BACKEND=local
################################################################################
### IMAGE GENERATION PROVIDER
################################################################################
### OPEN AI
# IMAGE_PROVIDER - Image provider (Example: dalle)
IMAGE_PROVIDER=dalle
### HUGGINGFACE
# STABLE DIFFUSION
# (Default URL: https://api-inference.huggingface.co/models/CompVis/stable-diffusion-v1-4)
# Set in image_gen.py)
# HUGGINGFACE_API_TOKEN - HuggingFace API token (Example: my-huggingface-api-token)
HUGGINGFACE_API_TOKEN=your-huggingface-api-token
################################################################################
### SEARCH PROVIDER
################################################################################
### GOOGLE
# GOOGLE_API_KEY - Google API key (Example: my-google-api-key)
# CUSTOM_SEARCH_ENGINE_ID - Custom search engine ID (Example: my-custom-search-engine-id)
GOOGLE_API_KEY=your-google-api-key
CUSTOM_SEARCH_ENGINE_ID=your-custom-search-engine-id
################################################################################
### TTS PROVIDER
################################################################################
### MAC OS
# USE_MAC_OS_TTS - Use Mac OS TTS or not (Default: False)
USE_MAC_OS_TTS=False
### STREAMELEMENTS
# USE_BRIAN_TTS - Use Brian TTS or not (Default: False)
USE_BRIAN_TTS=False
### ELEVENLABS
# ELEVENLABS_API_KEY - Eleven Labs API key (Example: my-elevenlabs-api-key)
# ELEVENLABS_VOICE_1_ID - Eleven Labs voice 1 ID (Example: my-voice-id-1)
# ELEVENLABS_VOICE_2_ID - Eleven Labs voice 2 ID (Example: my-voice-id-2)
ELEVENLABS_API_KEY=your-elevenlabs-api-key
ELEVENLABS_VOICE_1_ID=your-voice-id-1
ELEVENLABS_VOICE_2_ID=your-voice-id-2

12
.flake8 Normal file
View File

@@ -0,0 +1,12 @@
[flake8]
max-line-length = 88
extend-ignore = E203
exclude =
.tox,
__pycache__,
*.pyc,
.env
venv/*
.venv/*
reports/*
dist/*

View File

@@ -32,11 +32,11 @@ jobs:
- name: Lint with flake8 - name: Lint with flake8
continue-on-error: false continue-on-error: false
run: flake8 scripts/ tests/ --select E303,W293,W291,W292,E305,E231,E302 run: flake8 autogpt/ tests/ --select E303,W293,W291,W292,E305,E231,E302
- name: Run unittest tests with coverage - name: Run unittest tests with coverage
run: | run: |
coverage run --source=scripts -m unittest discover tests coverage run --source=autogpt -m unittest discover tests
- name: Generate coverage report - name: Generate coverage report
run: | run: |

146
.gitignore vendored
View File

@@ -1,7 +1,8 @@
scripts/keys.py ## Original ignores
scripts/*json autogpt/keys.py
scripts/node_modules/ autogpt/*json
scripts/__pycache__/keys.cpython-310.pyc autogpt/node_modules/
autogpt/__pycache__/keys.cpython-310.pyc
package-lock.json package-lock.json
*.pyc *.pyc
auto_gpt_workspace/* auto_gpt_workspace/*
@@ -16,11 +17,138 @@ last_run_ai_settings.yaml
.idea/* .idea/*
auto-gpt.json auto-gpt.json
log.txt log.txt
log-ingestion.txt
logs
# Coverage reports # Byte-compiled / optimized / DLL files
.coverage __pycache__/
coverage.xml *.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
plugins/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/ htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
# For Macs Dev Environs: ignoring .Desktop Services_Store # Translations
.DS_Store *.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
.python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
llama-*
vicuna-*

10
.isort.cfg Normal file
View File

@@ -0,0 +1,10 @@
[settings]
profile = black
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88
skip = venv,env,node_modules,.env,.venv,dist
sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER

33
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,33 @@
repos:
- repo: https://github.com/sourcery-ai/sourcery
rev: v1.1.0 # Get the latest tag from https://github.com/sourcery-ai/sourcery/tags
hooks:
- id: sourcery
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v0.9.2
hooks:
- id: check-added-large-files
args: [ '--maxkb=500' ]
- id: check-byte-order-marker
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: debug-statements
- repo: local
hooks:
- id: isort
name: isort-local
entry: isort
language: python
types: [ python ]
exclude: .+/(dist|.venv|venv|build)/.+
pass_filenames: true
- id: black
name: black-local
entry: black
language: python
types: [ python ]
exclude: .+/(dist|.venv|venv|build)/.+
pass_filenames: true

71
.sourcery.yaml Normal file
View File

@@ -0,0 +1,71 @@
# 🪄 This is your project's Sourcery configuration file.
# You can use it to get Sourcery working in the way you want, such as
# ignoring specific refactorings, skipping directories in your project,
# or writing custom rules.
# 📚 For a complete reference to this file, see the documentation at
# https://docs.sourcery.ai/Configuration/Project-Settings/
# This file was auto-generated by Sourcery on 2023-02-25 at 21:07.
version: '1' # The schema version of this config file
ignore: # A list of paths or files which Sourcery will ignore.
- .git
- venv
- .venv
- build
- dist
- env
- .env
- .tox
rule_settings:
enable:
- default
- gpsg
disable: [] # A list of rule IDs Sourcery will never suggest.
rule_types:
- refactoring
- suggestion
- comment
python_version: '3.9' # A string specifying the lowest Python version your project supports. Sourcery will not suggest refactorings requiring a higher Python version.
# rules: # A list of custom rules Sourcery will include in its analysis.
# - id: no-print-statements
# description: Do not use print statements in the test directory.
# pattern: print(...)
# language: python
# replacement:
# condition:
# explanation:
# paths:
# include:
# - test
# exclude:
# - conftest.py
# tests: []
# tags: []
# rule_tags: {} # Additional rule tags.
# metrics:
# quality_threshold: 25.0
# github:
# labels: []
# ignore_labels:
# - sourcery-ignore
# request_review: author
# sourcery_branch: sourcery/{base_branch}
# clone_detection:
# min_lines: 3
# min_duplicates: 2
# identical_clones_only: false
# proxy:
# url:
# ssl_certs_file:
# no_ssl_verify: false

View File

@@ -8,41 +8,49 @@ To contribute to this GitHub project, you can follow these steps:
``` ```
git clone https://github.com/<YOUR-GITHUB-USERNAME>/Auto-GPT git clone https://github.com/<YOUR-GITHUB-USERNAME>/Auto-GPT
``` ```
3. Create a new branch for your changes using the following command: 3. Install the project requirements
```
pip install -r requirements.txt
```
4. Install pre-commit hooks
```
pre-commit install
```
5. Create a new branch for your changes using the following command:
``` ```
git checkout -b "branch-name" git checkout -b "branch-name"
``` ```
4. Make your changes to the code or documentation. 6. Make your changes to the code or documentation.
- Example: Improve User Interface or Add Documentation. - Example: Improve User Interface or Add Documentation.
5. Add the changes to the staging area using the following command: 7. Add the changes to the staging area using the following command:
``` ```
git add . git add .
``` ```
6. Commit the changes with a meaningful commit message using the following command: 8. Commit the changes with a meaningful commit message using the following command:
``` ```
git commit -m "your commit message" git commit -m "your commit message"
``` ```
7. Push the changes to your forked repository using the following command: 9. Push the changes to your forked repository using the following command:
``` ```
git push origin branch-name git push origin branch-name
``` ```
8. Go to the GitHub website and navigate to your forked repository. 10. Go to the GitHub website and navigate to your forked repository.
9. Click the "New pull request" button. 11. Click the "New pull request" button.
10. Select the branch you just pushed to and the branch you want to merge into on the original repository. 12. Select the branch you just pushed to and the branch you want to merge into on the original repository.
11. Add a description of your changes and click the "Create pull request" button. 13. Add a description of your changes and click the "Create pull request" button.
12. Wait for the project maintainer to review your changes and provide feedback. 14. Wait for the project maintainer to review your changes and provide feedback.
13. Make any necessary changes based on feedback and repeat steps 5-12 until your changes are accepted and merged into the main project. 15. Make any necessary changes based on feedback and repeat steps 5-12 until your changes are accepted and merged into the main project.
14. Once your changes are merged, you can update your forked repository and local copy of the repository with the following commands: 16. Once your changes are merged, you can update your forked repository and local copy of the repository with the following commands:
``` ```
git fetch upstream git fetch upstream

View File

@@ -1,7 +1,23 @@
# Use an official Python base image from the Docker Hub
FROM python:3.11-slim FROM python:3.11-slim
ENV PIP_NO_CACHE_DIR=yes
WORKDIR /app # Set environment variables
COPY requirements.txt . ENV PIP_NO_CACHE_DIR=yes \
RUN pip install -r requirements.txt PYTHONUNBUFFERED=1 \
COPY scripts/ . PYTHONDONTWRITEBYTECODE=1
ENTRYPOINT ["python", "main.py"]
# Create a non-root user and set permissions
RUN useradd --create-home appuser
WORKDIR /home/appuser
RUN chown appuser:appuser /home/appuser
USER appuser
# Copy the requirements.txt file and install the requirements
COPY --chown=appuser:appuser requirements.txt .
RUN pip install --no-cache-dir --user -r requirements.txt
# Copy the application files
COPY --chown=appuser:appuser autogpt/ .
# Set the entrypoint
ENTRYPOINT ["python", "-m", "autogpt"]

124
README.md
View File

@@ -1,9 +1,13 @@
# Auto-GPT: An Autonomous GPT-4 Experiment # Auto-GPT: An Autonomous GPT-4 Experiment
### 🔴 🔴 🔴 Urgent: USE `stable` not `master` 🔴 🔴 🔴
We've improved our workflow. `master` will often be **in a broken state**.
Download the latest `stable` release here: https://github.com/Torantulino/Auto-GPT/releases/latest
This information SUPERCEDES any following information. Takes precedence. Do this not that.
![GitHub Repo stars](https://img.shields.io/github/stars/Torantulino/auto-gpt?style=social) ![GitHub Repo stars](https://img.shields.io/github/stars/Torantulino/auto-gpt?style=social)
![Twitter Follow](https://img.shields.io/twitter/follow/siggravitas?style=social) [![Twitter Follow](https://img.shields.io/twitter/follow/siggravitas?style=social)](https://twitter.com/SigGravitas)
[![Discord Follow](https://dcbadge.vercel.app/api/server/PQ7VX6TY4t?style=flat)](https://discord.gg/PQ7VX6TY4t) [![Discord Follow](https://dcbadge.vercel.app/api/server/autogpt?style=flat)](https://discord.gg/autogpt)
[![Unit Tests](https://github.com/Torantulino/Auto-GPT/actions/workflows/ci.yml/badge.svg)](https://github.com/Torantulino/Auto-GPT/actions/workflows/unit_tests.yml) [![Unit Tests](https://github.com/Torantulino/Auto-GPT/actions/workflows/ci.yml/badge.svg)](https://github.com/Torantulino/Auto-GPT/actions/workflows/ci.yml)
Auto-GPT is an experimental open-source application showcasing the capabilities of the GPT-4 language model. This program, driven by GPT-4, chains together LLM "thoughts", to autonomously achieve whatever goal you set. As one of the first examples of GPT-4 running fully autonomously, Auto-GPT pushes the boundaries of what is possible with AI. Auto-GPT is an experimental open-source application showcasing the capabilities of the GPT-4 language model. This program, driven by GPT-4, chains together LLM "thoughts", to autonomously achieve whatever goal you set. As one of the first examples of GPT-4 running fully autonomously, Auto-GPT pushes the boundaries of what is possible with AI.
@@ -46,6 +50,7 @@ Your support is greatly appreciated
- [Setting up environment variables](#setting-up-environment-variables-1) - [Setting up environment variables](#setting-up-environment-variables-1)
- [Setting Your Cache Type](#setting-your-cache-type) - [Setting Your Cache Type](#setting-your-cache-type)
- [View Memory Usage](#view-memory-usage) - [View Memory Usage](#view-memory-usage)
- [🧠 Memory pre-seeding](#memory-pre-seeding)
- [💀 Continuous Mode ⚠️](#-continuous-mode-) - [💀 Continuous Mode ⚠️](#-continuous-mode-)
- [GPT3.5 ONLY Mode](#gpt35-only-mode) - [GPT3.5 ONLY Mode](#gpt35-only-mode)
- [🖼 Image Generation](#-image-generation) - [🖼 Image Generation](#-image-generation)
@@ -65,12 +70,15 @@ Your support is greatly appreciated
## 📋 Requirements ## 📋 Requirements
- [Python 3.8 or later](https://www.tutorialspoint.com/how-to-install-python-in-windows) - environments(just choose one)
- [vscode + devcontainer](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers): It has been configured in the .devcontainer folder and can be used directly
- [Python 3.8 or later](https://www.tutorialspoint.com/how-to-install-python-in-windows)
- [OpenAI API key](https://platform.openai.com/account/api-keys) - [OpenAI API key](https://platform.openai.com/account/api-keys)
- [PINECONE API key](https://www.pinecone.io/)
Optional: Optional:
- [PINECONE API key](https://www.pinecone.io/) (If you want Pinecone backed memory)
- ElevenLabs Key (If you want the AI to speak) - ElevenLabs Key (If you want the AI to speak)
## 💾 Installation ## 💾 Installation
@@ -115,15 +123,15 @@ pip install -r requirements.txt
## 🔧 Usage ## 🔧 Usage
1. Run the `main.py` Python script in your terminal: 1. Run the `autogpt` Python module in your terminal:
_(Type this into your CMD window)_ _(Type this into your CMD window)_
``` ```
python scripts/main.py python -m autogpt
``` ```
2. After each of AUTO-GPT's actions, type "NEXT COMMAND" to authorise them to continue. 2. After each of action, enter 'y' to authorise command, 'y -N' to run N continuous commands, 'n' to exit program, or enter additional feedback for the AI.
3. To exit the program, type "exit" and press Enter.
### Logs ### Logs
@@ -132,15 +140,37 @@ You will find activity and error logs in the folder `./output/logs`
To output debug logs: To output debug logs:
``` ```
python scripts/main.py --debug python -m autogpt --debug
``` ```
### Docker
You can also build this into a docker image and run it:
```
docker build -t autogpt .
docker run -it --env-file=./.env -v $PWD/auto_gpt_workspace:/app/auto_gpt_workspace autogpt
```
You can pass extra arguments, for instance, running with `--gpt3only` and `--continuous` mode:
```
docker run -it --env-file=./.env -v $PWD/auto_gpt_workspace:/app/auto_gpt_workspace autogpt --gpt3only --continuous
```
### Command Line Arguments
Here are some common arguments you can use when running Auto-GPT:
> Replace anything in angled brackets (<>) to a value you want to specify
* `python scripts/main.py --help` to see a list of all available command line arguments.
* `python scripts/main.py --ai-settings <filename>` to run Auto-GPT with a different AI Settings file.
* `python scripts/main.py --use-memory <memory-backend>` to specify one of 3 memory backends: `local`, `redis`, `pinecone` or 'no_memory'.
> **NOTE**: There are shorthands for some of these flags, for example `-m` for `--use-memory`. Use `python scripts/main.py --help` for more information
## 🗣️ Speech Mode ## 🗣️ Speech Mode
Use this to use TTS for Auto-GPT Use this to use TTS for Auto-GPT
``` ```
python scripts/main.py --speak python -m autogpt --speak
``` ```
## 🔍 Google API Keys Configuration ## 🔍 Google API Keys Configuration
@@ -154,9 +184,10 @@ To use the `google_official_search` command, you need to set up your Google API
4. Go to the [APIs & Services Dashboard](https://console.cloud.google.com/apis/dashboard) and click "Enable APIs and Services". Search for "Custom Search API" and click on it, then click "Enable". 4. Go to the [APIs & Services Dashboard](https://console.cloud.google.com/apis/dashboard) and click "Enable APIs and Services". Search for "Custom Search API" and click on it, then click "Enable".
5. Go to the [Credentials](https://console.cloud.google.com/apis/credentials) page and click "Create Credentials". Choose "API Key". 5. Go to the [Credentials](https://console.cloud.google.com/apis/credentials) page and click "Create Credentials". Choose "API Key".
6. Copy the API key and set it as an environment variable named `GOOGLE_API_KEY` on your machine. See setting up environment variables below. 6. Copy the API key and set it as an environment variable named `GOOGLE_API_KEY` on your machine. See setting up environment variables below.
7. Go to the [Custom Search Engine](https://cse.google.com/cse/all) page and click "Add". 7. [Enable](https://console.developers.google.com/apis/api/customsearch.googleapis.com) the Custom Search API on your project. (Might need to wait few minutes to propagate)
8. Set up your search engine by following the prompts. You can choose to search the entire web or specific sites. 8. Go to the [Custom Search Engine](https://cse.google.com/cse/all) page and click "Add".
9. Once you've created your search engine, click on "Control Panel" and then "Basics". Copy the "Search engine ID" and set it as an environment variable named `CUSTOM_SEARCH_ENGINE_ID` on your machine. See setting up environment variables below. 9. Set up your search engine by following the prompts. You can choose to search the entire web or specific sites.
10. Once you've created your search engine, click on "Control Panel" and then "Basics". Copy the "Search engine ID" and set it as an environment variable named `CUSTOM_SEARCH_ENGINE_ID` on your machine. See setting up environment variables below.
_Remember that your free daily custom search quota allows only up to 100 searches. To increase this limit, you need to assign a billing account to the project to profit from up to 10K daily searches._ _Remember that your free daily custom search quota allows only up to 100 searches. To increase this limit, you need to assign a billing account to the project to profit from up to 10K daily searches._
@@ -225,7 +256,10 @@ Pinecone enables the storage of vast amounts of vector-based memory, allowing fo
### Setting up environment variables ### Setting up environment variables
Simply set them in the `.env` file. In the `.env` file set:
- `PINECONE_API_KEY`
- `PINECONE_ENV` (something like: us-east4-gcp)
- `MEMORY_BACKEND=pinecone`
Alternatively, you can set them from the command line (advanced): Alternatively, you can set them from the command line (advanced):
@@ -234,7 +268,7 @@ For Windows Users:
``` ```
setx PINECONE_API_KEY "YOUR_PINECONE_API_KEY" setx PINECONE_API_KEY "YOUR_PINECONE_API_KEY"
setx PINECONE_ENV "Your pinecone region" # something like: us-east4-gcp setx PINECONE_ENV "Your pinecone region" # something like: us-east4-gcp
setx MEMORY_BACKEND "pinecone"
``` ```
For macOS and Linux users: For macOS and Linux users:
@@ -242,7 +276,7 @@ For macOS and Linux users:
``` ```
export PINECONE_API_KEY="YOUR_PINECONE_API_KEY" export PINECONE_API_KEY="YOUR_PINECONE_API_KEY"
export PINECONE_ENV="Your pinecone region" # something like: us-east4-gcp export PINECONE_ENV="Your pinecone region" # something like: us-east4-gcp
export MEMORY_BACKEND="pinecone"
``` ```
## Weaviate Setup ## Weaviate Setup
@@ -281,6 +315,52 @@ To switch to either, change the `MEMORY_BACKEND` env variable to the value that
1. View memory usage by using the `--debug` flag :) 1. View memory usage by using the `--debug` flag :)
## 🧠 Memory pre-seeding
```
# python scripts/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.
options:
-h, --help show this help message and exit
--file FILE The file to ingest.
--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
# python scripts/data_ingestion.py --dir seed_data --init --overlap 200 --max_length 1000
```
This script located at scripts/data_ingestion.py, allows you to ingest files into memory and pre-seed it before running Auto-GPT.
Memory pre-seeding is a technique that involves ingesting relevant documents or data into the AI's memory so that it can use this information to generate more informed and accurate responses.
To pre-seed the memory, the content of each document is split into chunks of a specified maximum length with a specified overlap between chunks, and then each chunk is added to the memory backend set in the .env file. When the AI is prompted to recall information, it can then access those pre-seeded memories to generate more informed and accurate responses.
This technique is particularly useful when working with large amounts of data or when there is specific information that the AI needs to be able to access quickly.
By pre-seeding the memory, the AI can retrieve and use this information more efficiently, saving time, API call and improving the accuracy of its responses.
You could for example download the documentation of an API, a Github repository, etc. and ingest it into memory before running Auto-GPT.
⚠️ If you use Redis as your memory, make sure to run Auto-GPT with the WIPE_REDIS_ON_START set to False in your .env file.
For other memory backend, we currently forcefully wipe the memory when starting Auto-GPT. To ingest data with those memory backend, you can call the data_ingestion.py script anytime during an Auto-GPT run.
Memories will be available to the AI immediately as they are ingested, even if ingested while Auto-GPT is running.
In the example above, the script initializes the memory, ingests all files within the seed_data directory into memory with an overlap between chunks of 200 and a maximum length of each chunk of 4000.
Note that you can also use the --file argument to ingest a single file into memory and that the script will only ingest files within the auto_gpt_workspace directory.
You can adjust the max_length and overlap parameters to fine-tune the way the docuents are presented to the AI when it "recall" that memory:
- Adjusting the overlap value allows the AI to access more contextual information from each chunk when recalling information, but will result in more chunks being created and therefore increase memory backend usage and OpenAI API requests.
- Reducing the max_length value will create more chunks, which can save prompt tokens by allowing for more message history in the context, but will also increase the number of chunks.
- Increasing the max_length value will provide the AI with more contextual information from each chunk, reducing the number of chunks created and saving on OpenAI API requests. However, this may also use more prompt tokens and decrease the overall context available to the AI.
## 💀 Continuous Mode ⚠️ ## 💀 Continuous Mode ⚠️
Run the AI **without** user authorisation, 100% automated. Run the AI **without** user authorisation, 100% automated.
@@ -288,10 +368,10 @@ Continuous mode is not recommended.
It is potentially dangerous and may cause your AI to run forever or carry out actions you would not usually authorise. It is potentially dangerous and may cause your AI to run forever or carry out actions you would not usually authorise.
Use at your own risk. Use at your own risk.
1. Run the `main.py` Python script in your terminal: 1. Run the `autogpt` python module in your terminal:
``` ```
python scripts/main.py --continuous python -m autogpt --speak --continuous
``` ```
@@ -302,7 +382,7 @@ python scripts/main.py --continuous
If you don't have access to the GPT4 api, this mode will allow you to use Auto-GPT! If you don't have access to the GPT4 api, this mode will allow you to use Auto-GPT!
``` ```
python scripts/main.py --gpt3only python -m autogpt --speak --gpt3only
``` ```
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. 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.
@@ -375,8 +455,8 @@ This project uses [flake8](https://flake8.pycqa.org/en/latest/) for linting. We
To run the linter, run the following command: To run the linter, run the following command:
``` ```
flake8 scripts/ tests/ flake8 autogpt/ tests/
# Or, if you want to run flake8 with the same configuration as the CI: # Or, if you want to run flake8 with the same configuration as the CI:
flake8 scripts/ tests/ --select E303,W293,W291,W292,E305,E231,E302 flake8 autogpt/ tests/ --select E303,W293,W291,W292,E305,E231,E302
``` ```

572
autogpt/__main__.py Normal file
View File

@@ -0,0 +1,572 @@
import argparse
import json
import logging
import traceback
from colorama import Fore, Style
from autogpt import chat
from autogpt import commands as cmd
from autogpt import speak, utils
from autogpt.ai_config import AIConfig
from autogpt.config import Config
from autogpt.json_parser import fix_and_parse_json
from autogpt.logger import logger
from autogpt.memory import get_memory, get_supported_memory_backends
from autogpt.spinner import Spinner
cfg = Config()
config = None
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 .env or as an environment variable."
)
print("You can get your key from https://beta.openai.com/account/api-keys")
exit(1)
def attempt_to_fix_json_by_finding_outermost_brackets(json_string):
if cfg.speak_mode and cfg.debug_mode:
speak.say_text(
"I have received an invalid JSON response from the OpenAI API. "
"Trying to fix it now."
)
logger.typewriter_log("Attempting to fix JSON by finding outermost brackets\n")
try:
# Use regex to search for JSON objects
import regex
json_pattern = regex.compile(r"\{(?:[^{}]|(?R))*\}")
json_match = json_pattern.search(json_string)
if json_match:
# Extract the valid JSON object from the string
json_string = json_match.group(0)
logger.typewriter_log(
title="Apparently json was fixed.", title_color=Fore.GREEN
)
if cfg.speak_mode and cfg.debug_mode:
speak.say_text("Apparently json was fixed.")
else:
raise ValueError("No valid JSON object found")
except (json.JSONDecodeError, ValueError) as e:
if cfg.debug_mode:
logger.error("Error: Invalid JSON: %s\n", json_string)
if cfg.speak_mode:
speak.say_text("Didn't work. I will have to ignore this response then.")
logger.error("Error: Invalid JSON, setting it to empty JSON now.\n")
json_string = {}
return json_string
def print_assistant_thoughts(assistant_reply):
"""Prints the assistant's thoughts to the console"""
global ai_name
global cfg
try:
try:
# Parse and print Assistant response
assistant_reply_json = fix_and_parse_json(assistant_reply)
except json.JSONDecodeError as e:
logger.error("Error: Invalid JSON in assistant thoughts\n", assistant_reply)
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets(
assistant_reply
)
assistant_reply_json = fix_and_parse_json(assistant_reply_json)
# Check if assistant_reply_json is a string and attempt to parse it into a
# JSON object
if isinstance(assistant_reply_json, str):
try:
assistant_reply_json = json.loads(assistant_reply_json)
except json.JSONDecodeError as e:
logger.error("Error: Invalid JSON\n", assistant_reply)
assistant_reply_json = (
attempt_to_fix_json_by_finding_outermost_brackets(
assistant_reply_json
)
)
assistant_thoughts_reasoning = None
assistant_thoughts_plan = None
assistant_thoughts_speak = None
assistant_thoughts_criticism = None
assistant_thoughts = assistant_reply_json.get("thoughts", {})
assistant_thoughts_text = assistant_thoughts.get("text")
if assistant_thoughts:
assistant_thoughts_reasoning = assistant_thoughts.get("reasoning")
assistant_thoughts_plan = assistant_thoughts.get("plan")
assistant_thoughts_criticism = assistant_thoughts.get("criticism")
assistant_thoughts_speak = assistant_thoughts.get("speak")
logger.typewriter_log(
f"{ai_name.upper()} THOUGHTS:", Fore.YELLOW, f"{assistant_thoughts_text}"
)
logger.typewriter_log(
"REASONING:", Fore.YELLOW, f"{assistant_thoughts_reasoning}"
)
if assistant_thoughts_plan:
logger.typewriter_log("PLAN:", Fore.YELLOW, "")
# If it's a list, join it into a string
if isinstance(assistant_thoughts_plan, list):
assistant_thoughts_plan = "\n".join(assistant_thoughts_plan)
elif isinstance(assistant_thoughts_plan, dict):
assistant_thoughts_plan = str(assistant_thoughts_plan)
# Split the input_string using the newline character and dashes
lines = assistant_thoughts_plan.split("\n")
for line in lines:
line = line.lstrip("- ")
logger.typewriter_log("- ", Fore.GREEN, line.strip())
logger.typewriter_log(
"CRITICISM:", Fore.YELLOW, f"{assistant_thoughts_criticism}"
)
# Speak the assistant's thoughts
if cfg.speak_mode and assistant_thoughts_speak:
speak.say_text(assistant_thoughts_speak)
return assistant_reply_json
except json.decoder.JSONDecodeError:
call_stack = traceback.format_exc()
logger.error("Error: Invalid JSON\n", assistant_reply)
logger.error("Traceback: \n", call_stack)
if cfg.speak_mode:
speak.say_text(
"I have received an invalid JSON response from the OpenAI API."
" I cannot ignore this response."
)
# All other errors, return "Error: + error message"
except Exception:
call_stack = traceback.format_exc()
logger.error("Error: \n", call_stack)
def construct_prompt():
"""Construct the prompt for the AI to respond to"""
config: AIConfig = AIConfig.load(cfg.ai_settings_file)
if cfg.skip_reprompt and config.ai_name:
logger.typewriter_log("Name :", Fore.GREEN, config.ai_name)
logger.typewriter_log("Role :", Fore.GREEN, config.ai_role)
logger.typewriter_log("Goals:", Fore.GREEN, f"{config.ai_goals}")
elif config.ai_name:
logger.typewriter_log(
"Welcome back! ",
Fore.GREEN,
f"Would you like me to return to being {config.ai_name}?",
speak_text=True,
)
should_continue = utils.clean_input(
f"""Continue with the last settings?
Name: {config.ai_name}
Role: {config.ai_role}
Goals: {config.ai_goals}
Continue (y/n): """
)
if should_continue.lower() == "n":
config = AIConfig()
if not config.ai_name:
config = prompt_user()
config.save()
# Get rid of this global:
global ai_name
ai_name = config.ai_name
return config.construct_full_prompt()
def prompt_user():
"""Prompt the user for input"""
ai_name = ""
# Construct the prompt
logger.typewriter_log(
"Welcome to Auto-GPT! ",
Fore.GREEN,
"Enter the name of your AI and its role below. Entering nothing will load"
" defaults.",
speak_text=True,
)
# Get AI Name from User
logger.typewriter_log(
"Name your AI: ", Fore.GREEN, "For example, 'Entrepreneur-GPT'"
)
ai_name = utils.clean_input("AI Name: ")
if ai_name == "":
ai_name = "Entrepreneur-GPT"
logger.typewriter_log(
f"{ai_name} here!", Fore.LIGHTBLUE_EX, "I am at your service.", speak_text=True
)
# Get AI Role from User
logger.typewriter_log(
"Describe your AI's role: ",
Fore.GREEN,
"For example, 'an AI designed to autonomously develop and run businesses with"
" the sole goal of increasing your net worth.'",
)
ai_role = utils.clean_input(f"{ai_name} is: ")
if ai_role == "":
ai_role = "an AI designed to autonomously develop and run businesses with the"
" sole goal of increasing your net worth."
# Enter up to 5 goals for the AI
logger.typewriter_log(
"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 = utils.clean_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",
]
config = AIConfig(ai_name, ai_role, ai_goals)
return config
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)
parser = argparse.ArgumentParser(description="Process arguments.")
parser.add_argument(
"--continuous", "-c", action="store_true", help="Enable Continuous Mode"
)
parser.add_argument(
"--continuous-limit",
"-l",
type=int,
dest="continuous_limit",
help="Defines the number of times to run in continuous mode",
)
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")
parser.add_argument(
"--use-memory",
"-m",
dest="memory_type",
help="Defines which Memory backend to use",
)
parser.add_argument(
"--skip-reprompt",
"-y",
dest="skip_reprompt",
action="store_true",
help="Skips the re-prompting messages at the beginning of the script",
)
parser.add_argument(
"--ai-settings",
"-C",
dest="ai_settings_file",
help="Specifies which ai_settings.yaml file to use, will also automatically"
" skip the re-prompt.",
)
args = parser.parse_args()
if args.debug:
logger.typewriter_log("Debug Mode: ", Fore.GREEN, "ENABLED")
cfg.set_debug_mode(True)
if args.continuous:
logger.typewriter_log("Continuous Mode: ", Fore.RED, "ENABLED")
logger.typewriter_log(
"WARNING: ",
Fore.RED,
"Continuous mode is not recommended. It is potentially dangerous and may"
" cause your AI to run forever or carry out actions you would not usually"
" authorise. Use at your own risk.",
)
cfg.set_continuous_mode(True)
if args.continuous_limit:
logger.typewriter_log(
"Continuous Limit: ", Fore.GREEN, f"{args.continuous_limit}"
)
cfg.set_continuous_limit(args.continuous_limit)
# Check if continuous limit is used without continuous mode
if args.continuous_limit and not args.continuous:
parser.error("--continuous-limit can only be used with --continuous")
if args.speak:
logger.typewriter_log("Speak Mode: ", Fore.GREEN, "ENABLED")
cfg.set_speak_mode(True)
if args.gpt3only:
logger.typewriter_log("GPT3.5 Only Mode: ", Fore.GREEN, "ENABLED")
cfg.set_smart_llm_model(cfg.fast_llm_model)
if args.gpt4only:
logger.typewriter_log("GPT4 Only Mode: ", Fore.GREEN, "ENABLED")
cfg.set_fast_llm_model(cfg.smart_llm_model)
if args.memory_type:
supported_memory = get_supported_memory_backends()
chosen = args.memory_type
if not chosen in supported_memory:
logger.typewriter_log(
"ONLY THE FOLLOWING MEMORY BACKENDS ARE SUPPORTED: ",
Fore.RED,
f"{supported_memory}",
)
logger.typewriter_log("Defaulting to: ", Fore.YELLOW, cfg.memory_backend)
else:
cfg.memory_backend = chosen
if args.skip_reprompt:
logger.typewriter_log("Skip Re-prompt: ", Fore.GREEN, "ENABLED")
cfg.skip_reprompt = True
if args.ai_settings_file:
file = args.ai_settings_file
# Validate file
(validated, message) = utils.validate_yaml_file(file)
if not validated:
logger.typewriter_log("FAILED FILE VALIDATION", Fore.RED, message)
logger.double_check()
exit(1)
logger.typewriter_log("Using AI Settings File:", Fore.GREEN, file)
cfg.ai_settings_file = file
cfg.skip_reprompt = True
def main():
global ai_name, memory
# TODO: fill in llm values here
check_openai_api_key()
parse_arguments()
logger.set_level(logging.DEBUG if cfg.debug_mode else logging.INFO)
ai_name = ""
prompt = construct_prompt()
# print(prompt)
# Initialize variables
full_message_history = []
next_action_count = 0
# Make a constant:
user_input = (
"Determine which next command to use, and respond using the"
" format specified above:"
)
# Initialize memory and make sure it is empty.
# this is particularly important for indexing and referencing pinecone memory
memory = get_memory(cfg, init=True)
print(f"Using memory of type: {memory.__class__.__name__}")
agent = Agent(
ai_name=ai_name,
memory=memory,
full_message_history=full_message_history,
next_action_count=next_action_count,
prompt=prompt,
user_input=user_input,
)
agent.start_interaction_loop()
class Agent:
"""Agent class for interacting with Auto-GPT.
Attributes:
ai_name: The name of the agent.
memory: The memory object to use.
full_message_history: The full message history.
next_action_count: The number of actions to execute.
prompt: The prompt to use.
user_input: The user input.
"""
def __init__(
self,
ai_name,
memory,
full_message_history,
next_action_count,
prompt,
user_input,
):
self.ai_name = ai_name
self.memory = memory
self.full_message_history = full_message_history
self.next_action_count = next_action_count
self.prompt = prompt
self.user_input = user_input
def start_interaction_loop(self):
# Interaction Loop
loop_count = 0
command_name = None
arguments = None
while True:
# Discontinue if continuous limit is reached
loop_count += 1
if (
cfg.continuous_mode
and cfg.continuous_limit > 0
and loop_count > cfg.continuous_limit
):
logger.typewriter_log(
"Continuous Limit Reached: ", Fore.YELLOW, f"{cfg.continuous_limit}"
)
break
# Send message to AI, get response
with Spinner("Thinking... "):
assistant_reply = chat.chat_with_ai(
self.prompt,
self.user_input,
self.full_message_history,
self.memory,
cfg.fast_token_limit,
) # TODO: This hardcodes the model to use GPT3.5. Make this an argument
# Print Assistant thoughts
print_assistant_thoughts(assistant_reply)
# Get command name and arguments
try:
command_name, arguments = cmd.get_command(
attempt_to_fix_json_by_finding_outermost_brackets(assistant_reply)
)
if cfg.speak_mode:
speak.say_text(f"I want to execute {command_name}")
except Exception as e:
logger.error("Error: \n", str(e))
if not cfg.continuous_mode and self.next_action_count == 0:
### GET USER AUTHORIZATION TO EXECUTE COMMAND ###
# Get key press: Prompt the user to press enter to continue or escape
# to exit
self.user_input = ""
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL}"
f" ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
)
print(
"Enter 'y' to authorise command, 'y -N' to run N continuous"
" commands, 'n' to exit program, or enter feedback for"
f" {self.ai_name}...",
flush=True,
)
while True:
console_input = utils.clean_input(
Fore.MAGENTA + "Input:" + Style.RESET_ALL
)
if console_input.lower().rstrip() == "y":
self.user_input = "GENERATE NEXT COMMAND JSON"
break
elif console_input.lower().startswith("y -"):
try:
self.next_action_count = abs(
int(console_input.split(" ")[1])
)
self.user_input = "GENERATE NEXT COMMAND JSON"
except ValueError:
print(
"Invalid input format. Please enter 'y -n' where n"
" is the number of continuous tasks."
)
continue
break
elif console_input.lower() == "n":
self.user_input = "EXIT"
break
else:
self.user_input = console_input
command_name = "human_feedback"
break
if self.user_input == "GENERATE NEXT COMMAND JSON":
logger.typewriter_log(
"-=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-=",
Fore.MAGENTA,
"",
)
elif self.user_input == "EXIT":
print("Exiting...", flush=True)
break
else:
# Print command
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL}"
f" ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
)
# Execute command
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: {self.user_input}"
else:
result = (
f"Command {command_name} "
f"returned: {cmd.execute_command(command_name, arguments)}"
)
if self.next_action_count > 0:
self.next_action_count -= 1
memory_to_add = (
f"Assistant Reply: {assistant_reply} "
f"\nResult: {result} "
f"\nHuman Feedback: {self.user_input} "
)
self.memory.add(memory_to_add)
# Check if there's a result from the command append it to the message
# history
if result is not None:
self.full_message_history.append(
chat.create_chat_message("system", result)
)
logger.typewriter_log("SYSTEM: ", Fore.YELLOW, result)
else:
self.full_message_history.append(
chat.create_chat_message("system", "Unable to execute command")
)
logger.typewriter_log(
"SYSTEM: ", Fore.YELLOW, "Unable to execute command"
)
if __name__ == "__main__":
main()

304
autogpt/agent.py Normal file
View File

@@ -0,0 +1,304 @@
import json
import regex
import traceback
from colorama import Fore, Style
from autogpt.chat import chat_with_ai, create_chat_message
import autogpt.commands as cmd
from autogpt.config import Config
from autogpt.json_parser import fix_and_parse_json
from autogpt.logger import logger
from autogpt.speak import say_text
from autogpt.spinner import Spinner
from autogpt.utils import clean_input
class Agent:
"""Agent class for interacting with Auto-GPT.
Attributes:
ai_name: The name of the agent.
memory: The memory object to use.
full_message_history: The full message history.
next_action_count: The number of actions to execute.
prompt: The prompt to use.
user_input: The user input.
"""
def __init__(
self,
ai_name,
memory,
full_message_history,
next_action_count,
prompt,
user_input,
):
self.ai_name = ai_name
self.memory = memory
self.full_message_history = full_message_history
self.next_action_count = next_action_count
self.prompt = prompt
self.user_input = user_input
def start_interaction_loop(self):
# Interaction Loop
cfg = Config()
loop_count = 0
command_name = None
arguments = None
while True:
# Discontinue if continuous limit is reached
loop_count += 1
if (
cfg.continuous_mode
and cfg.continuous_limit > 0
and loop_count > cfg.continuous_limit
):
logger.typewriter_log(
"Continuous Limit Reached: ", Fore.YELLOW, f"{cfg.continuous_limit}"
)
break
# Send message to AI, get response
with Spinner("Thinking... "):
assistant_reply = chat_with_ai(
self.prompt,
self.user_input,
self.full_message_history,
self.memory,
cfg.fast_token_limit,
) # TODO: This hardcodes the model to use GPT3.5. Make this an argument
# Print Assistant thoughts
print_assistant_thoughts(self.ai_name, assistant_reply)
# Get command name and arguments
try:
command_name, arguments = cmd.get_command(
attempt_to_fix_json_by_finding_outermost_brackets(assistant_reply)
)
if cfg.speak_mode:
say_text(f"I want to execute {command_name}")
except Exception as e:
logger.error("Error: \n", str(e))
if not cfg.continuous_mode and self.next_action_count == 0:
### GET USER AUTHORIZATION TO EXECUTE COMMAND ###
# Get key press: Prompt the user to press enter to continue or escape
# to exit
self.user_input = ""
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} "
f"ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
)
print(
"Enter 'y' to authorise command, 'y -N' to run N continuous "
"commands, 'n' to exit program, or enter feedback for "
f"{self.ai_name}...",
flush=True,
)
while True:
console_input = clean_input(
Fore.MAGENTA + "Input:" + Style.RESET_ALL
)
if console_input.lower().rstrip() == "y":
self.user_input = "GENERATE NEXT COMMAND JSON"
break
elif console_input.lower().startswith("y -"):
try:
self.next_action_count = abs(
int(console_input.split(" ")[1])
)
self.user_input = "GENERATE NEXT COMMAND JSON"
except ValueError:
print(
"Invalid input format. Please enter 'y -n' where n is"
" the number of continuous tasks."
)
continue
break
elif console_input.lower() == "n":
self.user_input = "EXIT"
break
else:
self.user_input = console_input
command_name = "human_feedback"
break
if self.user_input == "GENERATE NEXT COMMAND JSON":
logger.typewriter_log(
"-=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-=",
Fore.MAGENTA,
"",
)
elif self.user_input == "EXIT":
print("Exiting...", flush=True)
break
else:
# Print command
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL}"
f" ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}",
)
# Execute command
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: {self.user_input}"
else:
result = (
f"Command {command_name} returned: "
f"{cmd.execute_command(command_name, arguments)}"
)
if self.next_action_count > 0:
self.next_action_count -= 1
memory_to_add = (
f"Assistant Reply: {assistant_reply} "
f"\nResult: {result} "
f"\nHuman Feedback: {self.user_input} "
)
self.memory.add(memory_to_add)
# Check if there's a result from the command append it to the message
# history
if result is not None:
self.full_message_history.append(create_chat_message("system", result))
logger.typewriter_log("SYSTEM: ", Fore.YELLOW, result)
else:
self.full_message_history.append(
create_chat_message("system", "Unable to execute command")
)
logger.typewriter_log(
"SYSTEM: ", Fore.YELLOW, "Unable to execute command"
)
def attempt_to_fix_json_by_finding_outermost_brackets(json_string):
cfg = Config()
if cfg.speak_mode and cfg.debug_mode:
say_text(
"I have received an invalid JSON response from the OpenAI API. "
"Trying to fix it now."
)
logger.typewriter_log("Attempting to fix JSON by finding outermost brackets\n")
try:
json_pattern = regex.compile(r"\{(?:[^{}]|(?R))*\}")
json_match = json_pattern.search(json_string)
if json_match:
# Extract the valid JSON object from the string
json_string = json_match.group(0)
logger.typewriter_log(
title="Apparently json was fixed.", title_color=Fore.GREEN
)
if cfg.speak_mode and cfg.debug_mode:
say_text("Apparently json was fixed.")
else:
raise ValueError("No valid JSON object found")
except (json.JSONDecodeError, ValueError):
if cfg.speak_mode:
say_text("Didn't work. I will have to ignore this response then.")
logger.error("Error: Invalid JSON, setting it to empty JSON now.\n")
json_string = {}
return json_string
def print_assistant_thoughts(ai_name, assistant_reply):
"""Prints the assistant's thoughts to the console"""
cfg = Config()
try:
try:
# Parse and print Assistant response
assistant_reply_json = fix_and_parse_json(assistant_reply)
except json.JSONDecodeError:
logger.error("Error: Invalid JSON in assistant thoughts\n", assistant_reply)
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets(
assistant_reply
)
if isinstance(assistant_reply_json, str):
assistant_reply_json = fix_and_parse_json(assistant_reply_json)
# Check if assistant_reply_json is a string and attempt to parse
# it into a JSON object
if isinstance(assistant_reply_json, str):
try:
assistant_reply_json = json.loads(assistant_reply_json)
except json.JSONDecodeError:
logger.error("Error: Invalid JSON\n", assistant_reply)
assistant_reply_json = (
attempt_to_fix_json_by_finding_outermost_brackets(
assistant_reply_json
)
)
assistant_thoughts_reasoning = None
assistant_thoughts_plan = None
assistant_thoughts_speak = None
assistant_thoughts_criticism = None
if not isinstance(assistant_reply_json, dict):
assistant_reply_json = {}
assistant_thoughts = assistant_reply_json.get("thoughts", {})
assistant_thoughts_text = assistant_thoughts.get("text")
if assistant_thoughts:
assistant_thoughts_reasoning = assistant_thoughts.get("reasoning")
assistant_thoughts_plan = assistant_thoughts.get("plan")
assistant_thoughts_criticism = assistant_thoughts.get("criticism")
assistant_thoughts_speak = assistant_thoughts.get("speak")
logger.typewriter_log(
f"{ai_name.upper()} THOUGHTS:", Fore.YELLOW, f"{assistant_thoughts_text}"
)
logger.typewriter_log(
"REASONING:", Fore.YELLOW, f"{assistant_thoughts_reasoning}"
)
if assistant_thoughts_plan:
logger.typewriter_log("PLAN:", Fore.YELLOW, "")
# If it's a list, join it into a string
if isinstance(assistant_thoughts_plan, list):
assistant_thoughts_plan = "\n".join(assistant_thoughts_plan)
elif isinstance(assistant_thoughts_plan, dict):
assistant_thoughts_plan = str(assistant_thoughts_plan)
# Split the input_string using the newline character and dashes
lines = assistant_thoughts_plan.split("\n")
for line in lines:
line = line.lstrip("- ")
logger.typewriter_log("- ", Fore.GREEN, line.strip())
logger.typewriter_log(
"CRITICISM:", Fore.YELLOW, f"{assistant_thoughts_criticism}"
)
# Speak the assistant's thoughts
if cfg.speak_mode and assistant_thoughts_speak:
say_text(assistant_thoughts_speak)
return assistant_reply_json
except json.decoder.JSONDecodeError:
logger.error("Error: Invalid JSON\n", assistant_reply)
if cfg.speak_mode:
say_text(
"I have received an invalid JSON response from the OpenAI API."
" I cannot ignore this response."
)
# All other errors, return "Error: + error message"
except Exception:
call_stack = traceback.format_exc()
logger.error("Error: \n", call_stack)

View File

@@ -1,4 +1,4 @@
from llm_utils import create_chat_completion from autogpt.llm_utils import create_chat_completion
next_key = 0 next_key = 0
agents = {} # key, (task, full_message_history, model) agents = {} # key, (task, full_message_history, model)
@@ -12,7 +12,9 @@ def create_agent(task, prompt, model):
global next_key global next_key
global agents global agents
messages = [{"role": "user", "content": prompt}, ] messages = [
{"role": "user", "content": prompt},
]
# Start GPT instance # Start GPT instance
agent_reply = create_chat_completion( agent_reply = create_chat_completion(

View File

@@ -1,6 +1,8 @@
import yaml
import data
import os import os
from typing import Type
import yaml
from autogpt.prompt import get_prompt
class AIConfig: class AIConfig:
@@ -13,7 +15,9 @@ class AIConfig:
ai_goals (list): The list of objectives the AI is supposed to complete. ai_goals (list): The list of objectives the AI is supposed to complete.
""" """
def __init__(self, ai_name: str="", ai_role: str="", ai_goals: list=[]) -> None: def __init__(
self, ai_name: str = "", ai_role: str = "", ai_goals: list = []
) -> None:
""" """
Initialize a class instance Initialize a class instance
@@ -30,24 +34,26 @@ class AIConfig:
self.ai_goals = ai_goals self.ai_goals = ai_goals
# Soon this will go in a folder where it remembers more stuff about the run(s) # Soon this will go in a folder where it remembers more stuff about the run(s)
SAVE_FILE = os.path.join(os.path.dirname(__file__), '..', 'ai_settings.yaml') SAVE_FILE = os.path.join(os.path.dirname(__file__), "..", "ai_settings.yaml")
@classmethod @classmethod
def load(cls: object, config_file: str=SAVE_FILE) -> object: def load(cls: "Type[AIConfig]", config_file: str = SAVE_FILE) -> "Type[AIConfig]":
""" """
Returns class object with parameters (ai_name, ai_role, ai_goals) loaded from yaml file if yaml file exists, Returns class object with parameters (ai_name, ai_role, ai_goals) loaded from
yaml file if yaml file exists,
else returns class with no parameters. else returns class with no parameters.
Parameters: Parameters:
cls (class object): An AIConfig Class object. cls (class object): An AIConfig Class object.
config_file (int): The path to the config yaml file. DEFAULT: "../ai_settings.yaml" config_file (int): The path to the config yaml file.
DEFAULT: "../ai_settings.yaml"
Returns: Returns:
cls (object): An instance of given cls object cls (object): An instance of given cls object
""" """
try: try:
with open(config_file) as file: with open(config_file, encoding="utf-8") as file:
config_params = yaml.load(file, Loader=yaml.FullLoader) config_params = yaml.load(file, Loader=yaml.FullLoader)
except FileNotFoundError: except FileNotFoundError:
config_params = {} config_params = {}
@@ -55,23 +61,28 @@ class AIConfig:
ai_name = config_params.get("ai_name", "") ai_name = config_params.get("ai_name", "")
ai_role = config_params.get("ai_role", "") ai_role = config_params.get("ai_role", "")
ai_goals = config_params.get("ai_goals", []) ai_goals = config_params.get("ai_goals", [])
# type: Type[AIConfig]
return cls(ai_name, ai_role, ai_goals) return cls(ai_name, ai_role, ai_goals)
def save(self, config_file: str=SAVE_FILE) -> None: def save(self, config_file: str = SAVE_FILE) -> None:
""" """
Saves the class parameters to the specified file yaml file path as a yaml file. Saves the class parameters to the specified file yaml file path as a yaml file.
Parameters: Parameters:
config_file(str): The path to the config yaml file. DEFAULT: "../ai_settings.yaml" config_file(str): The path to the config yaml file.
DEFAULT: "../ai_settings.yaml"
Returns: Returns:
None None
""" """
config = {"ai_name": self.ai_name, "ai_role": self.ai_role, "ai_goals": self.ai_goals} config = {
with open(config_file, "w") as file: "ai_name": self.ai_name,
yaml.dump(config, file) "ai_role": self.ai_role,
"ai_goals": self.ai_goals,
}
with open(config_file, "w", encoding="utf-8") as file:
yaml.dump(config, file, allow_unicode=True)
def construct_full_prompt(self) -> str: def construct_full_prompt(self) -> str:
""" """
@@ -81,15 +92,23 @@ class AIConfig:
None None
Returns: Returns:
full_prompt (str): A string containing the initial prompt for the user including the ai_name, ai_role and ai_goals. full_prompt (str): A string containing the initial prompt for the user
including the ai_name, ai_role and ai_goals.
""" """
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.""" 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 # 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): for i, goal in enumerate(self.ai_goals):
full_prompt += f"{i+1}. {goal}\n" full_prompt += f"{i+1}. {goal}\n"
full_prompt += f"\n\n{data.load_prompt()}" full_prompt += f"\n\n{get_prompt()}"
return full_prompt return full_prompt

View File

@@ -1,33 +1,37 @@
from typing import List, Optional
import json import json
from config import Config from typing import List
from call_ai_function import call_ai_function
from json_parser import fix_and_parse_json from autogpt.call_ai_function import call_ai_function
from autogpt.config import Config
cfg = Config() cfg = Config()
def evaluate_code(code: str) -> List[str]: def evaluate_code(code: str) -> List[str]:
""" """
A function that takes in a string and returns a response from create chat completion api call. A function that takes in a string and returns a response from create chat
completion api call.
Parameters: Parameters:
code (str): Code to be evaluated. code (str): Code to be evaluated.
Returns: Returns:
A result string from create chat completion. A list of suggestions to improve the code. A result string from create chat completion. A list of suggestions to
improve the code.
""" """
function_string = "def analyze_code(code: str) -> List[str]:" function_string = "def analyze_code(code: str) -> List[str]:"
args = [code] args = [code]
description_string = """Analyzes the given code and returns a list of suggestions for improvements.""" description_string = (
"Analyzes the given code and returns a list of suggestions" " for improvements."
)
result_string = call_ai_function(function_string, args, description_string) return call_ai_function(function_string, args, description_string)
return result_string
def improve_code(suggestions: List[str], code: str) -> str: def improve_code(suggestions: List[str], code: str) -> str:
""" """
A function that takes in code and suggestions and returns a response from create chat completion api call. A function that takes in code and suggestions and returns a response from create
chat completion api call.
Parameters: Parameters:
suggestions (List): A list of suggestions around what needs to be improved. suggestions (List): A list of suggestions around what needs to be improved.
@@ -40,28 +44,34 @@ def improve_code(suggestions: List[str], code: str) -> str:
"def generate_improved_code(suggestions: List[str], code: str) -> str:" "def generate_improved_code(suggestions: List[str], code: str) -> str:"
) )
args = [json.dumps(suggestions), code] args = [json.dumps(suggestions), code]
description_string = """Improves the provided code based on the suggestions provided, making no other changes.""" 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) return call_ai_function(function_string, args, description_string)
return result_string
def write_tests(code: str, focus: List[str]) -> str: def write_tests(code: str, focus: List[str]) -> str:
""" """
A function that takes in code and focus topics and returns a response from create chat completion api call. A function that takes in code and focus topics and returns a response from create
chat completion api call.
Parameters: Parameters:
focus (List): A list of suggestions around what needs to be improved. focus (List): A list of suggestions around what needs to be improved.
code (str): Code for test cases to be generated against. code (str): Code for test cases to be generated against.
Returns: Returns:
A result string from create chat completion. Test cases for the submitted code in response. A result string from create chat completion. Test cases for the submitted code
in response.
""" """
function_string = ( function_string = (
"def create_test_cases(code: str, focus: Optional[str] = None) -> str:" "def create_test_cases(code: str, focus: Optional[str] = None) -> str:"
) )
args = [code, json.dumps(focus)] args = [code, json.dumps(focus)]
description_string = """Generates test cases for the existing code, focusing on specific areas if required.""" 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) return call_ai_function(function_string, args, description_string)
return result_string

View File

@@ -1,10 +1,17 @@
from urllib.parse import urljoin, urlparse
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from config import Config
from llm_utils import create_chat_completion from autogpt.config import Config
from urllib.parse import urlparse, urljoin from autogpt.llm_utils import create_chat_completion
from autogpt.memory import get_memory
cfg = Config() cfg = Config()
memory = get_memory(cfg)
session = requests.Session()
session.headers.update({"User-Agent": cfg.user_agent})
# Function to check if the URL is valid # Function to check if the URL is valid
@@ -23,23 +30,28 @@ def sanitize_url(url):
# Define and check for local file address prefixes # Define and check for local file address prefixes
def check_local_file_access(url): def check_local_file_access(url):
local_prefixes = ['file:///', 'file://localhost', 'http://localhost', 'https://localhost'] local_prefixes = [
"file:///",
"file://localhost",
"http://localhost",
"https://localhost",
]
return any(url.startswith(prefix) for prefix in local_prefixes) return any(url.startswith(prefix) for prefix in local_prefixes)
def get_response(url, headers=cfg.user_agent_header, timeout=10): def get_response(url, timeout=10):
try: try:
# Restrict access to local files # Restrict access to local files
if check_local_file_access(url): if check_local_file_access(url):
raise ValueError('Access to local files is restricted') raise ValueError("Access to local files is restricted")
# Most basic check if the URL is valid: # Most basic check if the URL is valid:
if not url.startswith('http://') and not url.startswith('https://'): if not url.startswith("http://") and not url.startswith("https://"):
raise ValueError('Invalid URL format') raise ValueError("Invalid URL format")
sanitized_url = sanitize_url(url) sanitized_url = sanitize_url(url)
response = requests.get(sanitized_url, headers=headers, timeout=timeout) response = session.get(sanitized_url, timeout=timeout)
# Check if the response contains an HTTP error # Check if the response contains an HTTP error
if response.status_code >= 400: if response.status_code >= 400:
@@ -51,7 +63,8 @@ def get_response(url, headers=cfg.user_agent_header, timeout=10):
return None, "Error: " + str(ve) return None, "Error: " + str(ve)
except requests.exceptions.RequestException as re: except requests.exceptions.RequestException as re:
# Handle exceptions related to the HTTP request (e.g., connection errors, timeouts, etc.) # Handle exceptions related to the HTTP request
# (e.g., connection errors, timeouts, etc.)
return None, "Error: " + str(re) return None, "Error: " + str(re)
@@ -60,6 +73,8 @@ def scrape_text(url):
response, error_message = get_response(url) response, error_message = get_response(url)
if error_message: if error_message:
return error_message return error_message
if not response:
return "Error: Could not get response"
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
@@ -69,7 +84,7 @@ def scrape_text(url):
text = soup.get_text() text = soup.get_text()
lines = (line.strip() for line in text.splitlines()) lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" ")) chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = '\n'.join(chunk for chunk in chunks if chunk) text = "\n".join(chunk for chunk in chunks if chunk)
return text return text
@@ -77,8 +92,8 @@ def scrape_text(url):
def extract_hyperlinks(soup): def extract_hyperlinks(soup):
"""Extract hyperlinks from a BeautifulSoup object""" """Extract hyperlinks from a BeautifulSoup object"""
hyperlinks = [] hyperlinks = []
for link in soup.find_all('a', href=True): for link in soup.find_all("a", href=True):
hyperlinks.append((link.text, link['href'])) hyperlinks.append((link.text, link["href"]))
return hyperlinks return hyperlinks
@@ -95,7 +110,8 @@ def scrape_links(url):
response, error_message = get_response(url) response, error_message = get_response(url)
if error_message: if error_message:
return error_message return error_message
if not response:
return "Error: Could not get response"
soup = BeautifulSoup(response.text, "html.parser") soup = BeautifulSoup(response.text, "html.parser")
for script in soup(["script", "style"]): for script in soup(["script", "style"]):
@@ -106,7 +122,7 @@ def scrape_links(url):
return format_hyperlinks(hyperlinks) return format_hyperlinks(hyperlinks)
def split_text(text, max_length=8192): def split_text(text, max_length=cfg.browse_chunk_max_length):
"""Split text into chunks of a maximum length""" """Split text into chunks of a maximum length"""
paragraphs = text.split("\n") paragraphs = text.split("\n")
current_length = 0 current_length = 0
@@ -129,11 +145,13 @@ def create_message(chunk, question):
"""Create a message for the user to summarize a chunk of text""" """Create a message for the user to summarize a chunk of text"""
return { return {
"role": "user", "role": "user",
"content": f"\"\"\"{chunk}\"\"\" Using the above text, please answer the following question: \"{question}\" -- if the question cannot be answered using the text, please summarize the text." "content": f'"""{chunk}""" Using the above text, please answer the following'
f' question: "{question}" -- if the question cannot be answered using the'
" text, please summarize the text.",
} }
def summarize_text(text, question): def summarize_text(url, text, question):
"""Summarize text using the LLM model""" """Summarize text using the LLM model"""
if not text: if not text:
return "Error: No text to summarize" return "Error: No text to summarize"
@@ -145,15 +163,26 @@ def summarize_text(text, question):
chunks = list(split_text(text)) chunks = list(split_text(text))
for i, chunk in enumerate(chunks): for i, chunk in enumerate(chunks):
print(f"Adding chunk {i + 1} / {len(chunks)} to memory")
memory_to_add = f"Source: {url}\n" f"Raw content part#{i + 1}: {chunk}"
memory.add(memory_to_add)
print(f"Summarizing chunk {i + 1} / {len(chunks)}") print(f"Summarizing chunk {i + 1} / {len(chunks)}")
messages = [create_message(chunk, question)] messages = [create_message(chunk, question)]
summary = create_chat_completion( summary = create_chat_completion(
model=cfg.fast_llm_model, model=cfg.fast_llm_model,
messages=messages, messages=messages,
max_tokens=300, max_tokens=cfg.browse_summary_max_token,
) )
summaries.append(summary) summaries.append(summary)
print(f"Added chunk {i + 1} summary to memory")
memory_to_add = f"Source: {url}\n" f"Content summary part#{i + 1}: {summary}"
memory.add(memory_to_add)
print(f"Summarized {len(chunks)} chunks.") print(f"Summarized {len(chunks)} chunks.")
@@ -163,7 +192,7 @@ def summarize_text(text, question):
final_summary = create_chat_completion( final_summary = create_chat_completion(
model=cfg.fast_llm_model, model=cfg.fast_llm_model,
messages=messages, messages=messages,
max_tokens=300, max_tokens=cfg.browse_summary_max_token,
) )
return final_summary return final_summary

View File

@@ -1,30 +1,26 @@
from config import Config from autogpt.config import Config
from autogpt.llm_utils import create_chat_completion
cfg = Config() cfg = Config()
from llm_utils import create_chat_completion
# This is a magic function that can do anything with no-code. See # This is a magic function that can do anything with no-code. See
# https://github.com/Torantulino/AI-Functions for more info. # https://github.com/Torantulino/AI-Functions for more info.
def call_ai_function(function, args, description, model=None): def call_ai_function(function, args, description, model=None) -> str:
"""Call an AI function""" """Call an AI function"""
if model is None: if model is None:
model = cfg.smart_llm_model model = cfg.smart_llm_model
# For each arg, if any are None, convert to "None": # For each arg, if any are None, convert to "None":
args = [str(arg) if arg is not None else "None" for arg in args] args = [str(arg) if arg is not None else "None" for arg in args]
# parse args to comma seperated string # parse args to comma separated string
args = ", ".join(args) args = ", ".join(args)
messages = [ messages = [
{ {
"role": "system", "role": "system",
"content": f"You are now the following python function: ```# {description}\n{function}```\n\nOnly respond with your `return` value.", "content": f"You are now the following python function: ```# {description}"
f"\n{function}```\n\nOnly respond with your `return` value.",
}, },
{"role": "user", "content": args}, {"role": "user", "content": args},
] ]
response = create_chat_completion( return create_chat_completion(model=model, messages=messages, temperature=0)
model=model, messages=messages, temperature=0
)
return response

View File

@@ -1,11 +1,11 @@
import time import time
import openai
from dotenv import load_dotenv from openai.error import RateLimitError
from config import Config
import token_counter from autogpt import token_counter
from llm_utils import create_chat_completion from autogpt.config import Config
from logger import logger from autogpt.llm_utils import create_chat_completion
import logging from autogpt.logger import logger
cfg = Config() cfg = Config()
@@ -26,75 +26,105 @@ def create_chat_message(role, content):
def generate_context(prompt, relevant_memory, full_message_history, model): def generate_context(prompt, relevant_memory, full_message_history, model):
current_context = [ current_context = [
create_chat_message("system", prompt),
create_chat_message( create_chat_message(
"system", prompt), "system", f"The current time and date is {time.strftime('%c')}"
),
create_chat_message( create_chat_message(
"system", f"The current time and date is {time.strftime('%c')}"), "system",
create_chat_message( f"This reminds you of these events from your past:\n{relevant_memory}\n\n",
"system", f"This reminds you of these events from your past:\n{relevant_memory}\n\n")] ),
]
# Add messages from the full message history until we reach the token limit # Add messages from the full message history until we reach the token limit
next_message_to_add_index = len(full_message_history) - 1 next_message_to_add_index = len(full_message_history) - 1
insertion_index = len(current_context) insertion_index = len(current_context)
# Count the currently used tokens # Count the currently used tokens
current_tokens_used = token_counter.count_message_tokens(current_context, model) current_tokens_used = token_counter.count_message_tokens(current_context, model)
return next_message_to_add_index, current_tokens_used, insertion_index, current_context return (
next_message_to_add_index,
current_tokens_used,
insertion_index,
current_context,
)
# TODO: Change debug from hardcode to argument # TODO: Change debug from hardcode to argument
def chat_with_ai( def chat_with_ai(
prompt, prompt, user_input, full_message_history, permanent_memory, token_limit
user_input, ):
full_message_history, """Interact with the OpenAI API, sending the prompt, user input, message history,
permanent_memory, and permanent memory."""
token_limit):
"""Interact with the OpenAI API, sending the prompt, user input, message history, and permanent memory."""
while True: while True:
try: try:
""" """
Interact with the OpenAI API, sending the prompt, user input, message history, and permanent memory. Interact with the OpenAI API, sending the prompt, user input,
message history, and permanent memory.
Args: Args:
prompt (str): The prompt explaining the rules to the AI. prompt (str): The prompt explaining the rules to the AI.
user_input (str): The input from the user. user_input (str): The input from the user.
full_message_history (list): The list of all messages sent between the user and the AI. full_message_history (list): The list of all messages sent between the
permanent_memory (Obj): The memory object containing the permanent memory. user and the AI.
token_limit (int): The maximum number of tokens allowed in the API call. permanent_memory (Obj): The memory object containing the permanent
memory.
token_limit (int): The maximum number of tokens allowed in the API call.
Returns: Returns:
str: The AI's response. str: The AI's response.
""" """
model = cfg.fast_llm_model # TODO: Change model from hardcode to argument model = cfg.fast_llm_model # TODO: Change model from hardcode to argument
# Reserve 1000 tokens for the response # Reserve 1000 tokens for the response
logger.debug(f"Token limit: {token_limit}") logger.debug(f"Token limit: {token_limit}")
send_token_limit = token_limit - 1000 send_token_limit = token_limit - 1000
relevant_memory = permanent_memory.get_relevant(str(full_message_history[-9:]), 10) relevant_memory = (
""
if len(full_message_history) == 0
else permanent_memory.get_relevant(str(full_message_history[-9:]), 10)
)
logger.debug(f'Memory Stats: {permanent_memory.get_stats()}') logger.debug(f"Memory Stats: {permanent_memory.get_stats()}")
next_message_to_add_index, current_tokens_used, insertion_index, current_context = generate_context( (
prompt, relevant_memory, full_message_history, model) next_message_to_add_index,
current_tokens_used,
insertion_index,
current_context,
) = generate_context(prompt, relevant_memory, full_message_history, model)
while current_tokens_used > 2500: while current_tokens_used > 2500:
# remove memories until we are under 2500 tokens # remove memories until we are under 2500 tokens
relevant_memory = relevant_memory[1:] relevant_memory = relevant_memory[1:]
next_message_to_add_index, current_tokens_used, insertion_index, current_context = generate_context( (
prompt, relevant_memory, full_message_history, model) next_message_to_add_index,
current_tokens_used,
insertion_index,
current_context,
) = generate_context(
prompt, relevant_memory, full_message_history, model
)
current_tokens_used += token_counter.count_message_tokens([create_chat_message("user", user_input)], model) # Account for user input (appended later) current_tokens_used += token_counter.count_message_tokens(
[create_chat_message("user", user_input)], model
) # Account for user input (appended later)
while next_message_to_add_index >= 0: while next_message_to_add_index >= 0:
# print (f"CURRENT TOKENS USED: {current_tokens_used}") # print (f"CURRENT TOKENS USED: {current_tokens_used}")
message_to_add = full_message_history[next_message_to_add_index] message_to_add = full_message_history[next_message_to_add_index]
tokens_to_add = token_counter.count_message_tokens([message_to_add], model) tokens_to_add = token_counter.count_message_tokens(
[message_to_add], model
)
if current_tokens_used + tokens_to_add > send_token_limit: if current_tokens_used + tokens_to_add > send_token_limit:
break break
# Add the most recent message to the start of the current context, after the two system prompts. # Add the most recent message to the start of the current context,
current_context.insert(insertion_index, full_message_history[next_message_to_add_index]) # after the two system prompts.
current_context.insert(
insertion_index, full_message_history[next_message_to_add_index]
)
# Count the currently used tokens # Count the currently used tokens
current_tokens_used += tokens_to_add current_tokens_used += tokens_to_add
@@ -107,7 +137,9 @@ def chat_with_ai(
# Calculate remaining tokens # Calculate remaining tokens
tokens_remaining = token_limit - current_tokens_used tokens_remaining = token_limit - current_tokens_used
# assert tokens_remaining >= 0, "Tokens remaining is negative. This should never happen, please submit a bug report at https://www.github.com/Torantulino/Auto-GPT" # assert tokens_remaining >= 0, "Tokens remaining is negative.
# This should never happen, please submit a bug report at
# https://www.github.com/Torantulino/Auto-GPT"
# Debug print the current context # Debug print the current context
logger.debug(f"Token limit: {token_limit}") logger.debug(f"Token limit: {token_limit}")
@@ -122,7 +154,8 @@ def chat_with_ai(
logger.debug("") logger.debug("")
logger.debug("----------- END OF CONTEXT ----------------") logger.debug("----------- END OF CONTEXT ----------------")
# TODO: use a model defined elsewhere, so that model can contain temperature and other settings we care about # TODO: use a model defined elsewhere, so that model can contain
# temperature and other settings we care about
assistant_reply = create_chat_completion( assistant_reply = create_chat_completion(
model=model, model=model,
messages=current_context, messages=current_context,
@@ -130,15 +163,13 @@ def chat_with_ai(
) )
# Update full message history # Update full message history
full_message_history.append(create_chat_message("user", user_input))
full_message_history.append( full_message_history.append(
create_chat_message( create_chat_message("assistant", assistant_reply)
"user", user_input)) )
full_message_history.append(
create_chat_message(
"assistant", assistant_reply))
return assistant_reply return assistant_reply
except openai.error.RateLimitError: except RateLimitError:
# TODO: When we switch to langchain, this is built in # TODO: When we switch to langchain, this is built in
print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...") print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...")
time.sleep(10) time.sleep(10)

View File

@@ -1,23 +1,29 @@
import browse
import json import json
from memory import get_memory
import datetime import datetime
import agent_manager as agents import autogpt.agent_manager as agents
import speak from autogpt.config import Config
from config import Config from autogpt.json_parser import fix_and_parse_json
import ai_functions as ai from autogpt.image_gen import generate_image
from file_operations import read_file, write_to_file, append_to_file, delete_file, search_files
from execute_code import execute_python_file, execute_shell
from json_parser import fix_and_parse_json
from image_gen import generate_image
from duckduckgo_search import ddg from duckduckgo_search import ddg
from googleapiclient.discovery import build from autogpt.ai_functions import evaluate_code, improve_code, write_tests
from googleapiclient.errors import HttpError from autogpt.browse import scrape_links, scrape_text, summarize_text
from autogpt.execute_code import execute_python_file, execute_shell
from autogpt.file_operations import (
append_to_file,
delete_file,
read_file,
search_files,
write_to_file,
)
from autogpt.memory import get_memory
from autogpt.speak import say_text
from autogpt.web import browse_website
cfg = Config() cfg = Config()
def is_valid_int(value): def is_valid_int(value) -> bool:
try: try:
int(value) int(value)
return True return True
@@ -31,9 +37,14 @@ def get_command(response):
response_json = fix_and_parse_json(response) response_json = fix_and_parse_json(response)
if "command" not in response_json: if "command" not in response_json:
return "Error:" , "Missing 'command' object in JSON" return "Error:", "Missing 'command' object in JSON"
if not isinstance(response_json, dict):
return "Error:", f"'response_json' object is not dictionary {response_json}"
command = response_json["command"] command = response_json["command"]
if not isinstance(command, dict):
return "Error:", "'command' object is not a dictionary"
if "name" not in command: if "name" not in command:
return "Error:", "Missing 'name' field in 'command' object" return "Error:", "Missing 'name' field in 'command' object"
@@ -57,10 +68,11 @@ def execute_command(command_name, arguments):
try: try:
if command_name == "google": if command_name == "google":
# Check if the Google API key is set and use the official search method # Check if the Google API key is set and use the official search method
# If the API key is not set or has only whitespaces, use the unofficial search method # If the API key is not set or has only whitespaces, use the unofficial
if cfg.google_api_key and (cfg.google_api_key.strip() if cfg.google_api_key else None): # search method
key = cfg.google_api_key
if key and key.strip() and key != "your-google-api-key":
return google_official_search(arguments["input"]) return google_official_search(arguments["input"])
else: else:
return google_search(arguments["input"]) return google_search(arguments["input"])
@@ -68,9 +80,8 @@ def execute_command(command_name, arguments):
return memory.add(arguments["string"]) return memory.add(arguments["string"])
elif command_name == "start_agent": elif command_name == "start_agent":
return start_agent( return start_agent(
arguments["name"], arguments["name"], arguments["task"], arguments["prompt"]
arguments["task"], )
arguments["prompt"])
elif command_name == "message_agent": elif command_name == "message_agent":
return message_agent(arguments["key"], arguments["message"]) return message_agent(arguments["key"], arguments["message"])
elif command_name == "list_agents": elif command_name == "list_agents":
@@ -97,18 +108,22 @@ def execute_command(command_name, arguments):
# non-file is given, return instructions "Input should be a python # non-file is given, return instructions "Input should be a python
# filepath, write your code to file and try again" # filepath, write your code to file and try again"
elif command_name == "evaluate_code": elif command_name == "evaluate_code":
return ai.evaluate_code(arguments["code"]) return evaluate_code(arguments["code"])
elif command_name == "improve_code": elif command_name == "improve_code":
return ai.improve_code(arguments["suggestions"], arguments["code"]) return improve_code(arguments["suggestions"], arguments["code"])
elif command_name == "write_tests": elif command_name == "write_tests":
return ai.write_tests(arguments["code"], arguments.get("focus")) return write_tests(arguments["code"], arguments.get("focus"))
elif command_name == "execute_python_file": # Add this command elif command_name == "execute_python_file": # Add this command
return execute_python_file(arguments["file"]) return execute_python_file(arguments["file"])
elif command_name == "execute_shell": elif command_name == "execute_shell":
if cfg.execute_local_commands: if cfg.execute_local_commands:
return execute_shell(arguments["command_line"]) return execute_shell(arguments["command_line"])
else: else:
return "You are not allowed to run local shell commands. To execute shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' in your config. Do not attempt to bypass the restriction." return (
"You are not allowed to run local shell commands. To execute"
" shell commands, EXECUTE_LOCAL_COMMANDS must be set to 'True' "
"in your config. Do not attempt to bypass the restriction."
)
elif command_name == "generate_image": elif command_name == "generate_image":
return generate_image(arguments["prompt"]) return generate_image(arguments["prompt"])
elif command_name == "do_nothing": elif command_name == "do_nothing":
@@ -116,7 +131,11 @@ def execute_command(command_name, arguments):
elif command_name == "task_complete": elif command_name == "task_complete":
shutdown() shutdown()
else: else:
return f"Unknown command '{command_name}'. Please refer to the 'COMMANDS' list for available commands and only respond in the specified JSON format." return (
f"Unknown command '{command_name}'. Please refer to the 'COMMANDS'"
" list for available commands and only respond in the specified JSON"
" format."
)
# All errors, return "Error: + error message" # All errors, return "Error: + error message"
except Exception as e: except Exception as e:
return "Error: " + str(e) return "Error: " + str(e)
@@ -124,13 +143,17 @@ def execute_command(command_name, arguments):
def get_datetime(): def get_datetime():
"""Return the current date and time""" """Return the current date and time"""
return "Current date and time: " + \ return "Current date and time: " + datetime.datetime.now().strftime(
datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") "%Y-%m-%d %H:%M:%S"
)
def google_search(query, num_results=8): def google_search(query, num_results=8):
"""Return the results of a google search""" """Return the results of a google search"""
search_results = [] search_results = []
if not query:
return json.dumps(search_results)
for j in ddg(query, max_results=num_results): for j in ddg(query, max_results=num_results):
search_results.append(j) search_results.append(j)
@@ -139,9 +162,10 @@ def google_search(query, num_results=8):
def google_official_search(query, num_results=8): def google_official_search(query, num_results=8):
"""Return the results of a google search using the official Google API""" """Return the results of a google search using the official Google API"""
import json
from googleapiclient.discovery import build from googleapiclient.discovery import build
from googleapiclient.errors import HttpError from googleapiclient.errors import HttpError
import json
try: try:
# Get the Google API key and Custom Search Engine ID from the config file # Get the Google API key and Custom Search Engine ID from the config file
@@ -152,7 +176,11 @@ def google_official_search(query, num_results=8):
service = build("customsearch", "v1", developerKey=api_key) service = build("customsearch", "v1", developerKey=api_key)
# Send the search query and retrieve the results # Send the search query and retrieve the results
result = service.cse().list(q=query, cx=custom_search_engine_id, num=num_results).execute() result = (
service.cse()
.list(q=query, cx=custom_search_engine_id, num=num_results)
.execute()
)
# Extract the search result items from the response # Extract the search result items from the response
search_results = result.get("items", []) search_results = result.get("items", [])
@@ -165,7 +193,11 @@ def google_official_search(query, num_results=8):
error_details = json.loads(e.content.decode()) error_details = json.loads(e.content.decode())
# Check if the error is related to an invalid or missing API key # Check if the error is related to an invalid or missing API key
if error_details.get("error", {}).get("code") == 403 and "invalid API key" in error_details.get("error", {}).get("message", ""): if error_details.get("error", {}).get(
"code"
) == 403 and "invalid API key" in error_details.get("error", {}).get(
"message", ""
):
return "Error: The provided Google API key is invalid or missing." return "Error: The provided Google API key is invalid or missing."
else: else:
return f"Error: {e}" return f"Error: {e}"
@@ -174,77 +206,16 @@ def google_official_search(query, num_results=8):
return search_results_links return search_results_links
def browse_website(url, question):
"""Browse a website and return the summary and links"""
summary = get_text_summary(url, question)
links = get_hyperlinks(url)
# Limit links to 5
if len(links) > 5:
links = links[:5]
result = f"""Website Content Summary: {summary}\n\nLinks: {links}"""
return result
def get_text_summary(url, question): def get_text_summary(url, question):
"""Return the results of a google search""" """Return the results of a google search"""
text = browse.scrape_text(url) text = scrape_text(url)
summary = browse.summarize_text(text, question) summary = summarize_text(url, text, question)
return """ "Result" : """ + summary return """ "Result" : """ + summary
def get_hyperlinks(url): def get_hyperlinks(url):
"""Return the results of a google search""" """Return the results of a google search"""
link_list = browse.scrape_links(url) return 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]
print(_text)
return _text
else:
print("Invalid key, cannot delete memory.")
return None
def overwrite_memory(key, string):
"""Overwrite a memory with a given key and string"""
# Check if the key is a valid integer
if is_valid_int(key):
key_int = int(key)
# Check if the integer key is within the range of the permanent_memory list
if 0 <= key_int < len(mem.permanent_memory):
_text = "Overwriting memory with key " + str(key) + " and string " + string
# Overwrite the memory slot with the given integer key and string
mem.permanent_memory[key_int] = string
print(_text)
return _text
else:
print(f"Invalid key '{key}', out of range.")
return None
# Check if the key is a valid string
elif isinstance(key, str):
_text = "Overwriting memory with key " + key + " and string " + string
# Overwrite the memory slot with the given string key and string
mem.permanent_memory[key] = string
print(_text)
return _text
else:
print(f"Invalid key '{key}', must be an integer or a string.")
return None
def shutdown(): def shutdown():
@@ -255,8 +226,6 @@ def shutdown():
def start_agent(name, task, prompt, model=cfg.fast_llm_model): def start_agent(name, task, prompt, model=cfg.fast_llm_model):
"""Start an agent with a given name, task, and prompt""" """Start an agent with a given name, task, and prompt"""
global cfg
# Remove underscores from name # Remove underscores from name
voice_name = name.replace("_", " ") voice_name = name.replace("_", " ")
@@ -265,22 +234,20 @@ def start_agent(name, task, prompt, model=cfg.fast_llm_model):
# Create agent # Create agent
if cfg.speak_mode: if cfg.speak_mode:
speak.say_text(agent_intro, 1) say_text(agent_intro, 1)
key, ack = agents.create_agent(task, first_message, model) key, ack = agents.create_agent(task, first_message, model)
if cfg.speak_mode: if cfg.speak_mode:
speak.say_text(f"Hello {voice_name}. Your task is as follows. {task}.") say_text(f"Hello {voice_name}. Your task is as follows. {task}.")
# Assign task (prompt), get response # Assign task (prompt), get response
agent_response = message_agent(key, prompt) agent_response = agents.message_agent(key, prompt)
return f"Agent {name} created with key {key}. First response: {agent_response}" return f"Agent {name} created with key {key}. First response: {agent_response}"
def message_agent(key, message): def message_agent(key, message):
"""Message an agent with a given key and message""" """Message an agent with a given key and message"""
global cfg
# Check if the key is a valid integer # Check if the key is a valid integer
if is_valid_int(key): if is_valid_int(key):
agent_response = agents.message_agent(int(key), message) agent_response = agents.message_agent(int(key), message)
@@ -292,18 +259,16 @@ def message_agent(key, message):
# Speak response # Speak response
if cfg.speak_mode: if cfg.speak_mode:
speak.say_text(agent_response, 1) say_text(agent_response, 1)
return agent_response return agent_response
def list_agents(): def list_agents():
"""List all agents""" """List all agents"""
return agents.list_agents() return list_agents()
def delete_agent(key): def delete_agent(key):
"""Delete an agent with a given key""" """Delete an agent with a given key"""
result = agents.delete_agent(key) result = agents.delete_agent(key)
if not result: return f"Agent {key} deleted." if result else f"Agent {key} does not exist."
return f"Agent {key} does not exist."
return f"Agent {key} deleted."

View File

@@ -1,8 +1,10 @@
import abc import abc
import os import os
import openai import openai
import yaml import yaml
from dotenv import load_dotenv from dotenv import load_dotenv
# Load environment variables from .env file # Load environment variables from .env file
load_dotenv() load_dotenv()
@@ -17,9 +19,7 @@ class Singleton(abc.ABCMeta, type):
def __call__(cls, *args, **kwargs): def __call__(cls, *args, **kwargs):
"""Call method for the singleton metaclass.""" """Call method for the singleton metaclass."""
if cls not in cls._instances: if cls not in cls._instances:
cls._instances[cls] = super( cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
Singleton, cls).__call__(
*args, **kwargs)
return cls._instances[cls] return cls._instances[cls]
@@ -36,17 +36,24 @@ class Config(metaclass=Singleton):
"""Initialize the Config class""" """Initialize the Config class"""
self.debug_mode = False self.debug_mode = False
self.continuous_mode = False self.continuous_mode = False
self.continuous_limit = 0
self.speak_mode = False self.speak_mode = False
self.skip_reprompt = False
self.ai_settings_file = os.getenv("AI_SETTINGS_FILE", "ai_settings.yaml")
self.fast_llm_model = os.getenv("FAST_LLM_MODEL", "gpt-3.5-turbo") self.fast_llm_model = os.getenv("FAST_LLM_MODEL", "gpt-3.5-turbo")
self.smart_llm_model = os.getenv("SMART_LLM_MODEL", "gpt-4") self.smart_llm_model = os.getenv("SMART_LLM_MODEL", "gpt-4")
self.fast_token_limit = int(os.getenv("FAST_TOKEN_LIMIT", 4000)) self.fast_token_limit = int(os.getenv("FAST_TOKEN_LIMIT", 4000))
self.smart_token_limit = int(os.getenv("SMART_TOKEN_LIMIT", 8000)) self.smart_token_limit = int(os.getenv("SMART_TOKEN_LIMIT", 8000))
self.browse_chunk_max_length = int(os.getenv("BROWSE_CHUNK_MAX_LENGTH", 8192))
self.browse_summary_max_token = int(os.getenv("BROWSE_SUMMARY_MAX_TOKEN", 300))
self.openai_api_key = os.getenv("OPENAI_API_KEY") self.openai_api_key = os.getenv("OPENAI_API_KEY")
self.temperature = float(os.getenv("TEMPERATURE", "1")) self.temperature = float(os.getenv("TEMPERATURE", "1"))
self.use_azure = os.getenv("USE_AZURE") == 'True' self.use_azure = os.getenv("USE_AZURE") == "True"
self.execute_local_commands = os.getenv('EXECUTE_LOCAL_COMMANDS', 'False') == 'True' self.execute_local_commands = (
os.getenv("EXECUTE_LOCAL_COMMANDS", "False") == "True"
)
if self.use_azure: if self.use_azure:
self.load_azure_config() self.load_azure_config()
@@ -61,6 +68,9 @@ class Config(metaclass=Singleton):
self.use_mac_os_tts = False self.use_mac_os_tts = False
self.use_mac_os_tts = os.getenv("USE_MAC_OS_TTS") self.use_mac_os_tts = os.getenv("USE_MAC_OS_TTS")
self.use_brian_tts = False
self.use_brian_tts = os.getenv("USE_BRIAN_TTS")
self.google_api_key = os.getenv("GOOGLE_API_KEY") self.google_api_key = os.getenv("GOOGLE_API_KEY")
self.custom_search_engine_id = os.getenv("CUSTOM_SEARCH_ENGINE_ID") self.custom_search_engine_id = os.getenv("CUSTOM_SEARCH_ENGINE_ID")
@@ -82,15 +92,18 @@ class Config(metaclass=Singleton):
# User agent headers to use when browsing web # User agent headers to use when browsing web
# Some websites might just completely deny request with an error code if no user agent was found. # Some websites might just completely deny request with an error code if no user agent was found.
self.user_agent_header = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"} self.user_agent = os.getenv(
"USER_AGENT",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36",
)
self.redis_host = os.getenv("REDIS_HOST", "localhost") self.redis_host = os.getenv("REDIS_HOST", "localhost")
self.redis_port = os.getenv("REDIS_PORT", "6379") self.redis_port = os.getenv("REDIS_PORT", "6379")
self.redis_password = os.getenv("REDIS_PASSWORD", "") self.redis_password = os.getenv("REDIS_PASSWORD", "")
self.wipe_redis_on_start = os.getenv("WIPE_REDIS_ON_START", "True") == 'True' self.wipe_redis_on_start = os.getenv("WIPE_REDIS_ON_START", "True") == "True"
self.memory_index = os.getenv("MEMORY_INDEX", 'auto-gpt') self.memory_index = os.getenv("MEMORY_INDEX", "auto-gpt")
# Note that indexes must be created on db 0 in redis, this is not configurable. # Note that indexes must be created on db 0 in redis, this is not configurable.
self.memory_backend = os.getenv("MEMORY_BACKEND", 'local') self.memory_backend = os.getenv("MEMORY_BACKEND", "local")
# Initialize the OpenAI API client # Initialize the OpenAI API client
openai.api_key = self.openai_api_key openai.api_key = self.openai_api_key
@@ -107,15 +120,19 @@ class Config(metaclass=Singleton):
if model == self.fast_llm_model: if model == self.fast_llm_model:
return self.azure_model_to_deployment_id_map["fast_llm_model_deployment_id"] return self.azure_model_to_deployment_id_map["fast_llm_model_deployment_id"]
elif model == self.smart_llm_model: elif model == self.smart_llm_model:
return self.azure_model_to_deployment_id_map["smart_llm_model_deployment_id"] return self.azure_model_to_deployment_id_map[
"smart_llm_model_deployment_id"
]
elif model == "text-embedding-ada-002": elif model == "text-embedding-ada-002":
return self.azure_model_to_deployment_id_map["embedding_model_deployment_id"] return self.azure_model_to_deployment_id_map[
"embedding_model_deployment_id"
]
else: else:
return "" return ""
AZURE_CONFIG_FILE = os.path.join(os.path.dirname(__file__), '..', 'azure.yaml') AZURE_CONFIG_FILE = os.path.join(os.path.dirname(__file__), "..", "azure.yaml")
def load_azure_config(self, config_file: str=AZURE_CONFIG_FILE) -> None: def load_azure_config(self, config_file: str = AZURE_CONFIG_FILE) -> None:
""" """
Loads the configuration parameters for Azure hosting from the specified file path as a yaml file. Loads the configuration parameters for Azure hosting from the specified file path as a yaml file.
@@ -130,15 +147,25 @@ class Config(metaclass=Singleton):
config_params = yaml.load(file, Loader=yaml.FullLoader) config_params = yaml.load(file, Loader=yaml.FullLoader)
except FileNotFoundError: except FileNotFoundError:
config_params = {} config_params = {}
self.openai_api_type = os.getenv("OPENAI_API_TYPE", config_params.get("azure_api_type", "azure")) self.openai_api_type = os.getenv(
self.openai_api_base = os.getenv("OPENAI_AZURE_API_BASE", config_params.get("azure_api_base", "")) "OPENAI_API_TYPE", config_params.get("azure_api_type", "azure")
self.openai_api_version = os.getenv("OPENAI_AZURE_API_VERSION", config_params.get("azure_api_version", "")) )
self.openai_api_base = os.getenv(
"OPENAI_AZURE_API_BASE", config_params.get("azure_api_base", "")
)
self.openai_api_version = os.getenv(
"OPENAI_AZURE_API_VERSION", config_params.get("azure_api_version", "")
)
self.azure_model_to_deployment_id_map = config_params.get("azure_model_map", []) self.azure_model_to_deployment_id_map = config_params.get("azure_model_map", [])
def set_continuous_mode(self, value: bool): def set_continuous_mode(self, value: bool):
"""Set the continuous mode value.""" """Set the continuous mode value."""
self.continuous_mode = value self.continuous_mode = value
def set_continuous_limit(self, value: int):
"""Set the continuous limit value."""
self.continuous_limit = value
def set_speak_mode(self, value: bool): def set_speak_mode(self, value: bool):
"""Set the speak mode value.""" """Set the speak mode value."""
self.speak_mode = value self.speak_mode = value
@@ -159,6 +186,14 @@ class Config(metaclass=Singleton):
"""Set the smart token limit value.""" """Set the smart token limit value."""
self.smart_token_limit = value self.smart_token_limit = value
def set_browse_chunk_max_length(self, value: int):
"""Set the browse_website command chunk max length value."""
self.browse_chunk_max_length = value
def set_browse_summary_max_token(self, value: int):
"""Set the browse_website command summary max token value."""
self.browse_summary_max_token = value
def set_openai_api_key(self, value: str): def set_openai_api_key(self, value: str):
"""Set the OpenAI API key value.""" """Set the OpenAI API key value."""
self.openai_api_key = value self.openai_api_key = value

95
autogpt/data_ingestion.py Normal file
View File

@@ -0,0 +1,95 @@
import argparse
import logging
from autogpt.config import Config
from autogpt.file_operations import ingest_file, search_files
from autogpt.memory import get_memory
cfg = Config()
def configure_logging():
logging.basicConfig(
filename="log-ingestion.txt",
filemode="a",
format="%(asctime)s,%(msecs)d %(name)s %(levelname)s %(message)s",
datefmt="%H:%M:%S",
level=logging.DEBUG,
)
return logging.getLogger("AutoGPT-Ingestion")
def ingest_directory(directory, memory, args):
"""
Ingest all files in a directory by calling the ingest_file function for each file.
:param directory: The directory containing the files to ingest
:param memory: An object with an add() method to store the chunks in memory
"""
try:
files = search_files(directory)
for file in files:
ingest_file(file, memory, args.max_length, args.overlap)
except Exception as e:
print(f"Error while ingesting directory '{directory}': {str(e)}")
def main() -> None:
logger = configure_logging()
parser = argparse.ArgumentParser(
description="Ingest a file or a directory with multiple files into memory. "
"Make sure to set your .env before running this script."
)
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument("--file", type=str, help="The file to ingest.")
group.add_argument(
"--dir", type=str, help="The directory containing the files to ingest."
)
parser.add_argument(
"--init",
action="store_true",
help="Init the memory and wipe its content (default: False)",
default=False,
)
parser.add_argument(
"--overlap",
type=int,
help="The overlap size between chunks when ingesting files (default: 200)",
default=200,
)
parser.add_argument(
"--max_length",
type=int,
help="The max_length of each chunk when ingesting files (default: 4000)",
default=4000,
)
args = parser.parse_args()
# Initialize memory
memory = get_memory(cfg, init=args.init)
print("Using memory of type: " + memory.__class__.__name__)
if args.file:
try:
ingest_file(args.file, memory, args.max_length, args.overlap)
print(f"File '{args.file}' ingested successfully.")
except Exception as e:
logger.error(f"Error while ingesting file '{args.file}': {str(e)}")
print(f"Error while ingesting file '{args.file}': {str(e)}")
elif args.dir:
try:
ingest_directory(args.dir, memory, args)
print(f"Directory '{args.dir}' ingested successfully.")
except Exception as e:
logger.error(f"Error while ingesting directory '{args.dir}': {str(e)}")
print(f"Error while ingesting directory '{args.dir}': {str(e)}")
else:
print(
"Please provide either a file path (--file) or a directory name (--dir) inside the auto_gpt_workspace directory as input."
)
if __name__ == "__main__":
main()

105
autogpt/execute_code.py Normal file
View File

@@ -0,0 +1,105 @@
import os
import subprocess
import docker
from docker.errors import ImageNotFound
WORKSPACE_FOLDER = "auto_gpt_workspace"
def execute_python_file(file):
"""Execute a Python file in a Docker container and return the output"""
print(f"Executing file '{file}' in workspace '{WORKSPACE_FOLDER}'")
if not file.endswith(".py"):
return "Error: Invalid file type. Only .py files are allowed."
file_path = os.path.join(WORKSPACE_FOLDER, file)
if not os.path.isfile(file_path):
return f"Error: File '{file}' does not exist."
if we_are_running_in_a_docker_container():
result = subprocess.run(
f"python {file_path}", capture_output=True, encoding="utf8", shell=True
)
if result.returncode == 0:
return result.stdout
else:
return f"Error: {result.stderr}"
else:
try:
client = docker.from_env()
image_name = "python:3.10"
try:
client.images.get(image_name)
print(f"Image '{image_name}' found locally")
except ImageNotFound:
print(
f"Image '{image_name}' not found locally, pulling from Docker Hub"
)
# Use the low-level API to stream the pull response
low_level_client = docker.APIClient()
for line in low_level_client.pull(image_name, stream=True, decode=True):
# Print the status and progress, if available
status = line.get("status")
progress = line.get("progress")
if status and progress:
print(f"{status}: {progress}")
elif status:
print(status)
# You can replace 'python:3.8' with the desired Python image/version
# You can find available Python images on Docker Hub:
# https://hub.docker.com/_/python
container = client.containers.run(
image_name,
f"python {file}",
volumes={
os.path.abspath(WORKSPACE_FOLDER): {
"bind": "/workspace",
"mode": "ro",
}
},
working_dir="/workspace",
stderr=True,
stdout=True,
detach=True,
)
container.wait()
logs = container.logs().decode("utf-8")
container.remove()
# print(f"Execution complete. Output: {output}")
# print(f"Logs: {logs}")
return logs
except Exception as e:
return f"Error: {str(e)}"
def execute_shell(command_line):
current_dir = os.getcwd()
if WORKSPACE_FOLDER not in current_dir: # Change dir into workspace if necessary
work_dir = os.path.join(os.getcwd(), WORKSPACE_FOLDER)
os.chdir(work_dir)
print(f"Executing command '{command_line}' in working directory '{os.getcwd()}'")
result = subprocess.run(command_line, capture_output=True, shell=True)
output = f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
# Change back to whatever the prior working dir was
os.chdir(current_dir)
return output
def we_are_running_in_a_docker_container():
os.path.exists("/.dockerenv")

141
autogpt/file_operations.py Normal file
View File

@@ -0,0 +1,141 @@
import os
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)
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)
if os.path.commonprefix([base, norm_new_path]) != base:
raise ValueError("Attempted to access outside of working directory.")
return norm_new_path
def split_file(content, max_length=4000, overlap=0):
"""
Split text into chunks of a specified maximum length with a specified overlap
between chunks.
:param text: The input text to be split into chunks
:param max_length: The maximum length of each chunk,
default is 4000 (about 1k token)
:param overlap: The number of overlapping characters between chunks,
default is no overlap
:return: A generator yielding chunks of text
"""
start = 0
content_length = len(content)
while start < content_length:
end = start + max_length
if end + overlap < content_length:
chunk = content[start : end + overlap]
else:
chunk = content[start:content_length]
yield chunk
start += max_length - overlap
def read_file(filename) -> str:
"""Read a file and return the contents"""
try:
filepath = safe_join(working_directory, filename)
with open(filepath, "r", encoding="utf-8") as f:
content = f.read()
return content
except Exception as e:
return f"Error: {str(e)}"
def ingest_file(filename, memory, max_length=4000, overlap=200):
"""
Ingest a file by reading its content, splitting it into chunks with a specified
maximum length and overlap, and adding the chunks to the memory storage.
:param filename: The name of the file to ingest
:param memory: An object with an add() method to store the chunks in memory
:param max_length: The maximum length of each chunk, default is 4000
:param overlap: The number of overlapping characters between chunks, default is 200
"""
try:
print(f"Working with file {filename}")
content = read_file(filename)
content_length = len(content)
print(f"File length: {content_length} characters")
chunks = list(split_file(content, max_length=max_length, overlap=overlap))
num_chunks = len(chunks)
for i, chunk in enumerate(chunks):
print(f"Ingesting chunk {i + 1} / {num_chunks} into memory")
memory_to_add = (
f"Filename: {filename}\n" f"Content part#{i + 1}/{num_chunks}: {chunk}"
)
memory.add(memory_to_add)
print(f"Done ingesting {num_chunks} chunks from {filename}.")
except Exception as e:
print(f"Error while ingesting file '{filename}': {str(e)}")
def write_to_file(filename, text):
"""Write text to a file"""
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", encoding="utf-8") as f:
f.write(text)
return "File written to successfully."
except Exception as e:
return "Error: " + str(e)
def append_to_file(filename, text):
"""Append text to a file"""
try:
filepath = safe_join(working_directory, filename)
with open(filepath, "a") as f:
f.write(text)
return "Text appended successfully."
except Exception as e:
return "Error: " + str(e)
def delete_file(filename):
"""Delete a file"""
try:
filepath = safe_join(working_directory, filename)
os.remove(filepath)
return "File deleted successfully."
except Exception as e:
return "Error: " + str(e)
def search_files(directory):
found_files = []
if directory == "" or directory == "/":
search_directory = working_directory
else:
search_directory = safe_join(working_directory, directory)
for root, _, files in os.walk(search_directory):
for file in files:
if file.startswith("."):
continue
relative_path = os.path.relpath(os.path.join(root, file), working_directory)
found_files.append(relative_path)
return found_files

View File

@@ -1,24 +1,24 @@
import requests
import io import io
import os.path import os.path
from PIL import Image
from config import Config
import uuid import uuid
import openai
from base64 import b64decode from base64 import b64decode
import openai
import requests
from PIL import Image
from autogpt.config import Config
cfg = Config() cfg = Config()
working_directory = "auto_gpt_workspace" working_directory = "auto_gpt_workspace"
def generate_image(prompt): def generate_image(prompt):
filename = str(uuid.uuid4()) + ".jpg" filename = str(uuid.uuid4()) + ".jpg"
# DALL-E # DALL-E
if cfg.image_provider == 'dalle': if cfg.image_provider == "dalle":
openai.api_key = cfg.openai_api_key openai.api_key = cfg.openai_api_key
response = openai.Image.create( response = openai.Image.create(
@@ -38,14 +38,23 @@ def generate_image(prompt):
return "Saved to disk:" + filename return "Saved to disk:" + filename
# STABLE DIFFUSION # STABLE DIFFUSION
elif cfg.image_provider == 'sd': elif cfg.image_provider == "sd":
API_URL = (
API_URL = "https://api-inference.huggingface.co/models/CompVis/stable-diffusion-v1-4" "https://api-inference.huggingface.co/models/CompVis/stable-diffusion-v1-4"
)
if cfg.huggingface_api_token is None:
raise ValueError(
"You need to set your Hugging Face API token in the config file."
)
headers = {"Authorization": "Bearer " + cfg.huggingface_api_token} headers = {"Authorization": "Bearer " + cfg.huggingface_api_token}
response = requests.post(API_URL, headers=headers, json={ response = requests.post(
"inputs": prompt, API_URL,
}) headers=headers,
json={
"inputs": prompt,
},
)
image = Image.open(io.BytesIO(response.content)) image = Image.open(io.BytesIO(response.content))
print("Image Generated for prompt:" + prompt) print("Image Generated for prompt:" + prompt)

29
autogpt/js/overlay.js Normal file
View File

@@ -0,0 +1,29 @@
const overlay = document.createElement('div');
Object.assign(overlay.style, {
position: 'fixed',
zIndex: 999999,
top: 0,
left: 0,
width: '100%',
height: '100%',
background: 'rgba(0, 0, 0, 0.7)',
color: '#fff',
fontSize: '24px',
fontWeight: 'bold',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
});
const textContent = document.createElement('div');
Object.assign(textContent.style, {
textAlign: 'center',
});
textContent.textContent = 'AutoGPT Analyzing Page';
overlay.appendChild(textContent);
document.body.append(overlay);
document.body.style.overflow = 'hidden';
let dotCount = 0;
setInterval(() => {
textContent.textContent = 'AutoGPT Analyzing Page' + '.'.repeat(dotCount);
dotCount = (dotCount + 1) % 4;
}, 1000);

View File

@@ -1,9 +1,10 @@
import json import json
from typing import Any, Dict, Union from typing import Any, Dict, Union
from call_ai_function import call_ai_function
from config import Config from autogpt.call_ai_function import call_ai_function
from json_utils import correct_json from autogpt.config import Config
from logger import logger from autogpt.json_utils import correct_json
from autogpt.logger import logger
cfg = Config() cfg = Config()
@@ -11,7 +12,7 @@ JSON_SCHEMA = """
{ {
"command": { "command": {
"name": "command name", "name": "command name",
"args":{ "args": {
"arg name": "value" "arg name": "value"
} }
}, },
@@ -28,12 +29,11 @@ JSON_SCHEMA = """
def fix_and_parse_json( def fix_and_parse_json(
json_str: str, json_str: str, try_to_fix_with_gpt: bool = True
try_to_fix_with_gpt: bool = True
) -> Union[str, Dict[Any, Any]]: ) -> Union[str, Dict[Any, Any]]:
"""Fix and parse JSON string""" """Fix and parse JSON string"""
try: try:
json_str = json_str.replace('\t', '') json_str = json_str.replace("\t", "")
return json.loads(json_str) return json.loads(json_str)
except json.JSONDecodeError as _: # noqa: F841 except json.JSONDecodeError as _: # noqa: F841
try: try:
@@ -52,15 +52,17 @@ def fix_and_parse_json(
brace_index = json_str.index("{") brace_index = json_str.index("{")
json_str = json_str[brace_index:] json_str = json_str[brace_index:]
last_brace_index = json_str.rindex("}") last_brace_index = json_str.rindex("}")
json_str = json_str[:last_brace_index+1] json_str = json_str[: last_brace_index + 1]
return json.loads(json_str) return json.loads(json_str)
# Can throw a ValueError if there is no "{" or "}" in the json_str # Can throw a ValueError if there is no "{" or "}" in the json_str
except (json.JSONDecodeError, ValueError) as e: # noqa: F841 except (json.JSONDecodeError, ValueError) as e: # noqa: F841
if try_to_fix_with_gpt: if try_to_fix_with_gpt:
logger.warn("Warning: Failed to parse AI output, attempting to fix." logger.warn(
"\n If you see this warning frequently, it's likely that" "Warning: Failed to parse AI output, attempting to fix."
" your prompt is confusing the AI. Try changing it up" "\n If you see this warning frequently, it's likely that"
" slightly.") " your prompt is confusing the AI. Try changing it up"
" slightly."
)
# Now try to fix this up using the ai_functions # Now try to fix this up using the ai_functions
ai_fixed_json = fix_json(json_str, JSON_SCHEMA) ai_fixed_json = fix_json(json_str, JSON_SCHEMA)
@@ -80,11 +82,13 @@ def fix_json(json_str: str, schema: str) -> str:
# Try to fix the JSON using GPT: # Try to fix the JSON using GPT:
function_string = "def fix_json(json_str: str, schema:str=None) -> str:" function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
args = [f"'''{json_str}'''", f"'''{schema}'''"] args = [f"'''{json_str}'''", f"'''{schema}'''"]
description_string = "Fixes the provided JSON string to make it parseable"\ description_string = (
" and fully compliant with the provided schema.\n If an object or"\ "Fixes the provided JSON string to make it parseable"
" field specified in the schema isn't contained within the correct"\ " and fully compliant with the provided schema.\n If an object or"
" JSON, it is omitted.\n This function is brilliant at guessing"\ " field specified in the schema isn't contained within the correct"
" JSON, it is omitted.\n This function is brilliant at guessing"
" when the format is incorrect." " when the format is incorrect."
)
# If it doesn't already start with a "`", add one: # If it doesn't already start with a "`", add one:
if not json_str.startswith("`"): if not json_str.startswith("`"):

View File

@@ -1,6 +1,8 @@
import re
import json import json
from config import Config import re
from typing import Optional
from autogpt.config import Config
cfg = Config() cfg = Config()
@@ -17,7 +19,7 @@ def extract_char_position(error_message: str) -> int:
""" """
import re import re
char_pattern = re.compile(r'\(char (\d+)\)') char_pattern = re.compile(r"\(char (\d+)\)")
if match := char_pattern.search(error_message): if match := char_pattern.search(error_message):
return int(match[1]) return int(match[1])
else: else:
@@ -38,10 +40,8 @@ def add_quotes_to_property_names(json_string: str) -> str:
def replace_func(match): def replace_func(match):
return f'"{match.group(1)}":' return f'"{match.group(1)}":'
property_name_pattern = re.compile(r'(\w+):') property_name_pattern = re.compile(r"(\w+):")
corrected_json_string = property_name_pattern.sub( corrected_json_string = property_name_pattern.sub(replace_func, json_string)
replace_func,
json_string)
try: try:
json.loads(corrected_json_string) json.loads(corrected_json_string)
@@ -50,7 +50,7 @@ def add_quotes_to_property_names(json_string: str) -> str:
raise e raise e
def balance_braces(json_string: str) -> str: def balance_braces(json_string: str) -> Optional[str]:
""" """
Balance the braces in a JSON string. Balance the braces in a JSON string.
@@ -61,35 +61,34 @@ def balance_braces(json_string: str) -> str:
str: The JSON string with braces balanced. str: The JSON string with braces balanced.
""" """
open_braces_count = json_string.count('{') open_braces_count = json_string.count("{")
close_braces_count = json_string.count('}') close_braces_count = json_string.count("}")
while open_braces_count > close_braces_count: while open_braces_count > close_braces_count:
json_string += '}' json_string += "}"
close_braces_count += 1 close_braces_count += 1
while close_braces_count > open_braces_count: while close_braces_count > open_braces_count:
json_string = json_string.rstrip('}') json_string = json_string.rstrip("}")
close_braces_count -= 1 close_braces_count -= 1
try: try:
json.loads(json_string) json.loads(json_string)
return json_string return json_string
except json.JSONDecodeError as e: except json.JSONDecodeError:
pass pass
def fix_invalid_escape(json_str: str, error_message: str) -> str: def fix_invalid_escape(json_str: str, error_message: str) -> str:
while error_message.startswith('Invalid \\escape'): while error_message.startswith("Invalid \\escape"):
bad_escape_location = extract_char_position(error_message) bad_escape_location = extract_char_position(error_message)
json_str = json_str[:bad_escape_location] + \ json_str = json_str[:bad_escape_location] + json_str[bad_escape_location + 1 :]
json_str[bad_escape_location + 1:]
try: try:
json.loads(json_str) json.loads(json_str)
return json_str return json_str
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
if cfg.debug_mode: if cfg.debug_mode:
print('json loads error - fix invalid escape', e) print("json loads error - fix invalid escape", e)
error_message = str(e) error_message = str(e)
return json_str return json_str
@@ -109,18 +108,20 @@ def correct_json(json_str: str) -> str:
return json_str return json_str
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
if cfg.debug_mode: if cfg.debug_mode:
print('json loads error', e) print("json loads error", e)
error_message = str(e) error_message = str(e)
if error_message.startswith('Invalid \\escape'): if error_message.startswith("Invalid \\escape"):
json_str = fix_invalid_escape(json_str, error_message) json_str = fix_invalid_escape(json_str, error_message)
if error_message.startswith('Expecting property name enclosed in double quotes'): if error_message.startswith(
"Expecting property name enclosed in double quotes"
):
json_str = add_quotes_to_property_names(json_str) json_str = add_quotes_to_property_names(json_str)
try: try:
json.loads(json_str) json.loads(json_str)
return json_str return json_str
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
if cfg.debug_mode: if cfg.debug_mode:
print('json loads error - add quotes', e) print("json loads error - add quotes", e)
error_message = str(e) error_message = str(e)
if balanced_str := balance_braces(json_str): if balanced_str := balance_braces(json_str):
return balanced_str return balanced_str

69
autogpt/llm_utils.py Normal file
View File

@@ -0,0 +1,69 @@
import time
import openai
from openai.error import APIError, RateLimitError
from colorama import Fore
from autogpt.config import Config
cfg = Config()
openai.api_key = cfg.openai_api_key
# Overly simple abstraction until we create something better
# simple retry mechanism when getting a rate error or a bad gateway
def create_chat_completion(
messages, model=None, temperature=cfg.temperature, max_tokens=None
) -> str:
"""Create a chat completion using the OpenAI API"""
response = None
num_retries = 5
if cfg.debug_mode:
print(
Fore.GREEN
+ f"Creating chat completion with model {model}, temperature {temperature},"
f" max_tokens {max_tokens}" + Fore.RESET
)
for attempt in range(num_retries):
try:
if cfg.use_azure:
response = openai.ChatCompletion.create(
deployment_id=cfg.get_azure_deployment_id_for_model(model),
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
else:
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens,
)
break
except RateLimitError:
if cfg.debug_mode:
print(
Fore.RED + "Error: ",
"API Rate Limit Reached. Waiting 20 seconds..." + Fore.RESET,
)
time.sleep(20)
except APIError as e:
if e.http_status == 502:
if cfg.debug_mode:
print(
Fore.RED + "Error: ",
"API Bad gateway. Waiting 20 seconds..." + Fore.RESET,
)
time.sleep(20)
else:
raise
if attempt == num_retries - 1:
raise
if response is None:
raise RuntimeError("Failed to get response after 5 retries")
return response.choices[0].message["content"]

View File

@@ -4,34 +4,33 @@ import random
import re import re
import time import time
from logging import LogRecord from logging import LogRecord
from colorama import Fore
from colorama import Style from colorama import Fore, Style
import speak from autogpt import speak
from config import Config from autogpt.config import Config, Singleton
from config import Singleton
cfg = Config() cfg = Config()
''' """
Logger that handle titles in different colors. Logger that handle titles in different colors.
Outputs logs in console, activity.log, and errors.log Outputs logs in console, activity.log, and errors.log
For console handler: simulates typing For console handler: simulates typing
''' """
class Logger(metaclass=Singleton): class Logger(metaclass=Singleton):
def __init__(self): def __init__(self):
# create log directory if it doesn't exist # create log directory if it doesn't exist
log_dir = os.path.join('..', 'logs') this_files_dir_path = os.path.dirname(__file__)
log_dir = os.path.join(this_files_dir_path, "../logs")
if not os.path.exists(log_dir): if not os.path.exists(log_dir):
os.makedirs(log_dir) os.makedirs(log_dir)
log_file = "activity.log" log_file = "activity.log"
error_file = "error.log" error_file = "error.log"
console_formatter = AutoGptFormatter('%(title_color)s %(message)s') console_formatter = AutoGptFormatter("%(title_color)s %(message)s")
# Create a handler for console which simulate typing # Create a handler for console which simulate typing
self.typing_console_handler = TypingConsoleHandler() self.typing_console_handler = TypingConsoleHandler()
@@ -46,35 +45,35 @@ class Logger(metaclass=Singleton):
# Info handler in activity.log # Info handler in activity.log
self.file_handler = logging.FileHandler(os.path.join(log_dir, log_file)) self.file_handler = logging.FileHandler(os.path.join(log_dir, log_file))
self.file_handler.setLevel(logging.DEBUG) self.file_handler.setLevel(logging.DEBUG)
info_formatter = AutoGptFormatter('%(asctime)s %(levelname)s %(title)s %(message_no_color)s') info_formatter = AutoGptFormatter(
"%(asctime)s %(levelname)s %(title)s %(message_no_color)s"
)
self.file_handler.setFormatter(info_formatter) self.file_handler.setFormatter(info_formatter)
# Error handler error.log # Error handler error.log
error_handler = logging.FileHandler(os.path.join(log_dir, error_file)) error_handler = logging.FileHandler(os.path.join(log_dir, error_file))
error_handler.setLevel(logging.ERROR) error_handler.setLevel(logging.ERROR)
error_formatter = AutoGptFormatter( error_formatter = AutoGptFormatter(
'%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s %(message_no_color)s') "%(asctime)s %(levelname)s %(module)s:%(funcName)s:%(lineno)d %(title)s"
" %(message_no_color)s"
)
error_handler.setFormatter(error_formatter) error_handler.setFormatter(error_formatter)
self.typing_logger = logging.getLogger('TYPER') self.typing_logger = logging.getLogger("TYPER")
self.typing_logger.addHandler(self.typing_console_handler) self.typing_logger.addHandler(self.typing_console_handler)
self.typing_logger.addHandler(self.file_handler) self.typing_logger.addHandler(self.file_handler)
self.typing_logger.addHandler(error_handler) self.typing_logger.addHandler(error_handler)
self.typing_logger.setLevel(logging.DEBUG) self.typing_logger.setLevel(logging.DEBUG)
self.logger = logging.getLogger('LOGGER') self.logger = logging.getLogger("LOGGER")
self.logger.addHandler(self.console_handler) self.logger.addHandler(self.console_handler)
self.logger.addHandler(self.file_handler) self.logger.addHandler(self.file_handler)
self.logger.addHandler(error_handler) self.logger.addHandler(error_handler)
self.logger.setLevel(logging.DEBUG) self.logger.setLevel(logging.DEBUG)
def typewriter_log( def typewriter_log(
self, self, title="", title_color="", content="", speak_text=False, level=logging.INFO
title='', ):
title_color='',
content='',
speak_text=False,
level=logging.INFO):
if speak_text and cfg.speak_mode: if speak_text and cfg.speak_mode:
speak.say_text(f"{title}. {content}") speak.say_text(f"{title}. {content}")
@@ -84,41 +83,34 @@ class Logger(metaclass=Singleton):
else: else:
content = "" content = ""
self.typing_logger.log(level, content, extra={'title': title, 'color': title_color}) self.typing_logger.log(
level, content, extra={"title": title, "color": title_color}
)
def debug( def debug(
self, self,
message, message,
title='', title="",
title_color='', title_color="",
): ):
self._log(title, title_color, message, logging.DEBUG) self._log(title, title_color, message, logging.DEBUG)
def warn( def warn(
self, self,
message, message,
title='', title="",
title_color='', title_color="",
): ):
self._log(title, title_color, message, logging.WARN) self._log(title, title_color, message, logging.WARN)
def error( def error(self, title, message=""):
self,
title,
message=''
):
self._log(title, Fore.RED, message, logging.ERROR) self._log(title, Fore.RED, message, logging.ERROR)
def _log( def _log(self, title="", title_color="", message="", level=logging.INFO):
self,
title='',
title_color='',
message='',
level=logging.INFO):
if message: if message:
if isinstance(message, list): if isinstance(message, list):
message = " ".join(message) message = " ".join(message)
self.logger.log(level, message, extra={'title': title, 'color': title_color}) self.logger.log(level, message, extra={"title": title, "color": title_color})
def set_level(self, level): def set_level(self, level):
self.logger.setLevel(level) self.logger.setLevel(level)
@@ -126,14 +118,19 @@ class Logger(metaclass=Singleton):
def double_check(self, additionalText=None): def double_check(self, additionalText=None):
if not additionalText: if not additionalText:
additionalText = "Please ensure you've setup and configured everything correctly. Read https://github.com/Torantulino/Auto-GPT#readme to double check. You can also create a github issue or join the discord and ask there!" additionalText = (
"Please ensure you've setup and configured everything"
" correctly. Read https://github.com/Torantulino/Auto-GPT#readme to "
"double check. You can also create a github issue or join the discord"
" and ask there!"
)
self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText) self.typewriter_log("DOUBLE CHECK CONFIGURATION", Fore.YELLOW, additionalText)
''' """
Output stream to console using simulated typing Output stream to console using simulated typing
''' """
class TypingConsoleHandler(logging.StreamHandler): class TypingConsoleHandler(logging.StreamHandler):
@@ -159,7 +156,7 @@ class TypingConsoleHandler(logging.StreamHandler):
class ConsoleHandler(logging.StreamHandler): class ConsoleHandler(logging.StreamHandler):
def emit(self, record): def emit(self, record) -> None:
msg = self.format(record) msg = self.format(record)
try: try:
print(msg) print(msg)
@@ -172,21 +169,27 @@ class AutoGptFormatter(logging.Formatter):
Allows to handle custom placeholders 'title_color' and 'message_no_color'. Allows to handle custom placeholders 'title_color' and 'message_no_color'.
To use this formatter, make sure to pass 'color', 'title' as log extras. To use this formatter, make sure to pass 'color', 'title' as log extras.
""" """
def format(self, record: LogRecord) -> str: def format(self, record: LogRecord) -> str:
if (hasattr(record, 'color')): if hasattr(record, "color"):
record.title_color = getattr(record, 'color') + getattr(record, 'title') + " " + Style.RESET_ALL record.title_color = (
getattr(record, "color")
+ getattr(record, "title")
+ " "
+ Style.RESET_ALL
)
else: else:
record.title_color = getattr(record, 'title') record.title_color = getattr(record, "title")
if hasattr(record, 'msg'): if hasattr(record, "msg"):
record.message_no_color = remove_color_codes(getattr(record, 'msg')) record.message_no_color = remove_color_codes(getattr(record, "msg"))
else: else:
record.message_no_color = '' record.message_no_color = ""
return super().format(record) return super().format(record)
def remove_color_codes(s: str) -> str: def remove_color_codes(s: str) -> str:
ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])') ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
return ansi_escape.sub('', s) return ansi_escape.sub("", s)
logger = Logger() logger = Logger()

View File

@@ -1,20 +1,22 @@
from memory.local import LocalCache from autogpt.memory.local import LocalCache
from memory.no_memory import NoMemory from autogpt.memory.no_memory import NoMemory
# List of supported memory backends # List of supported memory backends
# Add a backend to this list if the import attempt is successful # Add a backend to this list if the import attempt is successful
supported_memory = ['local'] supported_memory = ["local", "no_memory"]
try: try:
from memory.redismem import RedisMemory from autogpt.memory.redismem import RedisMemory
supported_memory.append('redis')
supported_memory.append("redis")
except ImportError: except ImportError:
print("Redis not installed. Skipping import.") print("Redis not installed. Skipping import.")
RedisMemory = None RedisMemory = None
try: try:
from memory.pinecone import PineconeMemory from autogpt.memory.pinecone import PineconeMemory
supported_memory.append('pinecone')
supported_memory.append("pinecone")
except ImportError: except ImportError:
print("Pinecone not installed. Skipping import.") print("Pinecone not installed. Skipping import.")
PineconeMemory = None PineconeMemory = None
@@ -29,16 +31,20 @@ def get_memory(cfg, init=False):
memory = None memory = None
if cfg.memory_backend == "pinecone": if cfg.memory_backend == "pinecone":
if not PineconeMemory: if not PineconeMemory:
print("Error: Pinecone is not installed. Please install pinecone" print(
" to use Pinecone as a memory backend.") "Error: Pinecone is not installed. Please install pinecone"
" to use Pinecone as a memory backend."
)
else: else:
memory = PineconeMemory(cfg) memory = PineconeMemory(cfg)
if init: if init:
memory.clear() memory.clear()
elif cfg.memory_backend == "redis": elif cfg.memory_backend == "redis":
if not RedisMemory: if not RedisMemory:
print("Error: Redis is not installed. Please install redis-py to" print(
" use Redis as a memory backend.") "Error: Redis is not installed. Please install redis-py to"
" use Redis as a memory backend."
)
else: else:
memory = RedisMemory(cfg) memory = RedisMemory(cfg)
elif cfg.memory_backend == "weaviate": elif cfg.memory_backend == "weaviate":
@@ -62,11 +68,4 @@ def get_supported_memory_backends():
return supported_memory return supported_memory
__all__ = [ __all__ = ["get_memory", "LocalCache", "RedisMemory", "PineconeMemory", "WeaviateMemory", "NoMemory"]
"get_memory",
"LocalCache",
"RedisMemory",
"PineconeMemory",
"WeaviateMemory"
"NoMemory"
]

View File

@@ -1,16 +1,23 @@
"""Base class for memory providers.""" """Base class for memory providers."""
import abc import abc
from config import AbstractSingleton, Config
import openai import openai
from autogpt.config import AbstractSingleton, Config
cfg = Config() cfg = Config()
def get_ada_embedding(text): def get_ada_embedding(text):
text = text.replace("\n", " ") text = text.replace("\n", " ")
if cfg.use_azure: if cfg.use_azure:
return openai.Embedding.create(input=[text], engine=cfg.get_azure_deployment_id_for_model("text-embedding-ada-002"))["data"][0]["embedding"] return openai.Embedding.create(
input=[text],
engine=cfg.get_azure_deployment_id_for_model("text-embedding-ada-002"),
)["data"][0]["embedding"]
else: 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"]
class MemoryProviderSingleton(AbstractSingleton): class MemoryProviderSingleton(AbstractSingleton):

View File

@@ -1,10 +1,11 @@
import dataclasses import dataclasses
import orjson
from typing import Any, List, Optional
import numpy as np
import os import os
from memory.base import MemoryProviderSingleton, get_ada_embedding from typing import Any, List, Optional
import numpy as np
import orjson
from autogpt.memory.base import MemoryProviderSingleton, get_ada_embedding
EMBED_DIM = 1536 EMBED_DIM = 1536
SAVE_OPTIONS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_SERIALIZE_DATACLASS SAVE_OPTIONS = orjson.OPT_SERIALIZE_NUMPY | orjson.OPT_SERIALIZE_DATACLASS
@@ -23,16 +24,15 @@ class CacheContent:
class LocalCache(MemoryProviderSingleton): class LocalCache(MemoryProviderSingleton):
# on load, load our database # on load, load our database
def __init__(self, cfg) -> None: def __init__(self, cfg) -> None:
self.filename = f"{cfg.memory_index}.json" self.filename = f"{cfg.memory_index}.json"
if os.path.exists(self.filename): if os.path.exists(self.filename):
try: try:
with open(self.filename, 'w+b') as f: with open(self.filename, "w+b") as f:
file_content = f.read() file_content = f.read()
if not file_content.strip(): if not file_content.strip():
file_content = b'{}' file_content = b"{}"
f.write(file_content) f.write(file_content)
loaded = orjson.loads(file_content) loaded = orjson.loads(file_content)
@@ -41,7 +41,9 @@ class LocalCache(MemoryProviderSingleton):
print(f"Error: The file '{self.filename}' is not in JSON format.") print(f"Error: The file '{self.filename}' is not in JSON format.")
self.data = CacheContent() self.data = CacheContent()
else: else:
print(f"Warning: The file '{self.filename}' does not exist. Local memory would not be saved to a file.") print(
f"Warning: The file '{self.filename}' does not exist. Local memory would not be saved to a file."
)
self.data = CacheContent() self.data = CacheContent()
def add(self, text: str): def add(self, text: str):
@@ -54,7 +56,7 @@ class LocalCache(MemoryProviderSingleton):
Returns: None Returns: None
""" """
if 'Command Error:' in text: if "Command Error:" in text:
return "" return ""
self.data.texts.append(text) self.data.texts.append(text)
@@ -70,11 +72,8 @@ class LocalCache(MemoryProviderSingleton):
axis=0, axis=0,
) )
with open(self.filename, 'wb') as f: with open(self.filename, "wb") as f:
out = orjson.dumps( out = orjson.dumps(self.data, option=SAVE_OPTIONS)
self.data,
option=SAVE_OPTIONS
)
f.write(out) f.write(out)
return text return text
@@ -99,7 +98,7 @@ class LocalCache(MemoryProviderSingleton):
return self.get_relevant(data, 1) return self.get_relevant(data, 1)
def get_relevant(self, text: str, k: int) -> List[Any]: def get_relevant(self, text: str, k: int) -> List[Any]:
"""" """ "
matrix-vector mult to find score-for-each-row-of-matrix matrix-vector mult to find score-for-each-row-of-matrix
get indices for top-k winning scores get indices for top-k winning scores
return texts for those indices return texts for those indices

View File

@@ -1,6 +1,6 @@
from typing import Optional, List, Any from typing import Optional, List, Any
from memory.base import MemoryProviderSingleton from autogpt.memory.base import MemoryProviderSingleton
class NoMemory(MemoryProviderSingleton): class NoMemory(MemoryProviderSingleton):

View File

@@ -1,9 +1,9 @@
import pinecone import pinecone
from memory.base import MemoryProviderSingleton, get_ada_embedding
from logger import logger
from colorama import Fore, Style from colorama import Fore, Style
from autogpt.logger import logger
from autogpt.memory.base import MemoryProviderSingleton, get_ada_embedding
class PineconeMemory(MemoryProviderSingleton): class PineconeMemory(MemoryProviderSingleton):
def __init__(self, cfg): def __init__(self, cfg):
@@ -22,13 +22,21 @@ class PineconeMemory(MemoryProviderSingleton):
try: try:
pinecone.whoami() pinecone.whoami()
except Exception as e: except Exception as e:
logger.typewriter_log("FAILED TO CONNECT TO PINECONE", Fore.RED, Style.BRIGHT + str(e) + Style.RESET_ALL) logger.typewriter_log(
logger.double_check("Please ensure you have setup and configured Pinecone properly for use. " + "FAILED TO CONNECT TO PINECONE",
f"You can check out {Fore.CYAN + Style.BRIGHT}https://github.com/Torantulino/Auto-GPT#-pinecone-api-key-setup{Style.RESET_ALL} to ensure you've set up everything correctly.") Fore.RED,
Style.BRIGHT + str(e) + Style.RESET_ALL,
)
logger.double_check(
"Please ensure you have setup and configured Pinecone properly for use. "
+ f"You can check out {Fore.CYAN + Style.BRIGHT}https://github.com/Torantulino/Auto-GPT#-pinecone-api-key-setup{Style.RESET_ALL} to ensure you've set up everything correctly."
)
exit(1) exit(1)
if table_name not in pinecone.list_indexes(): if table_name not in pinecone.list_indexes():
pinecone.create_index(table_name, dimension=dimension, metric=metric, pod_type=pod_type) pinecone.create_index(
table_name, dimension=dimension, metric=metric, pod_type=pod_type
)
self.index = pinecone.Index(table_name) self.index = pinecone.Index(table_name)
def add(self, data): def add(self, data):
@@ -53,9 +61,11 @@ class PineconeMemory(MemoryProviderSingleton):
:param num_relevant: The number of relevant data to return. Defaults to 5 :param num_relevant: The number of relevant data to return. Defaults to 5
""" """
query_embedding = get_ada_embedding(data) query_embedding = get_ada_embedding(data)
results = self.index.query(query_embedding, top_k=num_relevant, include_metadata=True) results = self.index.query(
query_embedding, top_k=num_relevant, include_metadata=True
)
sorted_results = sorted(results.matches, key=lambda x: x.score) sorted_results = sorted(results.matches, key=lambda x: x.score)
return [str(item['metadata']["raw_text"]) for item in sorted_results] return [str(item["metadata"]["raw_text"]) for item in sorted_results]
def get_stats(self): def get_stats(self):
return self.index.describe_index_stats() return self.index.describe_index_stats()

View File

@@ -1,26 +1,22 @@
"""Redis memory provider.""" """Redis memory provider."""
from typing import Any, List, Optional from typing import Any, List, Optional
import redis
from redis.commands.search.field import VectorField, TextField
from redis.commands.search.query import Query
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
import numpy as np import numpy as np
import redis
from memory.base import MemoryProviderSingleton, get_ada_embedding
from logger import logger
from colorama import Fore, Style from colorama import Fore, Style
from redis.commands.search.field import TextField, VectorField
from redis.commands.search.indexDefinition import IndexDefinition, IndexType
from redis.commands.search.query import Query
from autogpt.logger import logger
from autogpt.memory.base import MemoryProviderSingleton, get_ada_embedding
SCHEMA = [ SCHEMA = [
TextField("data"), TextField("data"),
VectorField( VectorField(
"embedding", "embedding",
"HNSW", "HNSW",
{ {"TYPE": "FLOAT32", "DIM": 1536, "DISTANCE_METRIC": "COSINE"},
"TYPE": "FLOAT32",
"DIM": 1536,
"DISTANCE_METRIC": "COSINE"
}
), ),
] ]
@@ -43,7 +39,7 @@ class RedisMemory(MemoryProviderSingleton):
host=redis_host, host=redis_host,
port=redis_port, port=redis_port,
password=redis_password, password=redis_password,
db=0 # Cannot be changed db=0, # Cannot be changed
) )
self.cfg = cfg self.cfg = cfg
@@ -51,9 +47,15 @@ class RedisMemory(MemoryProviderSingleton):
try: try:
self.redis.ping() self.redis.ping()
except redis.ConnectionError as e: except redis.ConnectionError as e:
logger.typewriter_log("FAILED TO CONNECT TO REDIS", Fore.RED, Style.BRIGHT + str(e) + Style.RESET_ALL) logger.typewriter_log(
logger.double_check("Please ensure you have setup and configured Redis properly for use. " + "FAILED TO CONNECT TO REDIS",
f"You can check out {Fore.CYAN + Style.BRIGHT}https://github.com/Torantulino/Auto-GPT#redis-setup{Style.RESET_ALL} to ensure you've set up everything correctly.") Fore.RED,
Style.BRIGHT + str(e) + Style.RESET_ALL,
)
logger.double_check(
"Please ensure you have setup and configured Redis properly for use. "
+ f"You can check out {Fore.CYAN + Style.BRIGHT}https://github.com/Torantulino/Auto-GPT#redis-setup{Style.RESET_ALL} to ensure you've set up everything correctly."
)
exit(1) exit(1)
if cfg.wipe_redis_on_start: if cfg.wipe_redis_on_start:
@@ -62,15 +64,13 @@ class RedisMemory(MemoryProviderSingleton):
self.redis.ft(f"{cfg.memory_index}").create_index( self.redis.ft(f"{cfg.memory_index}").create_index(
fields=SCHEMA, fields=SCHEMA,
definition=IndexDefinition( definition=IndexDefinition(
prefix=[f"{cfg.memory_index}:"], prefix=[f"{cfg.memory_index}:"], index_type=IndexType.HASH
index_type=IndexType.HASH ),
) )
)
except Exception as e: except Exception as e:
print("Error creating Redis search index: ", e) print("Error creating Redis search index: ", e)
existing_vec_num = self.redis.get(f'{cfg.memory_index}-vec_num') existing_vec_num = self.redis.get(f"{cfg.memory_index}-vec_num")
self.vec_num = int(existing_vec_num.decode('utf-8')) if\ self.vec_num = int(existing_vec_num.decode("utf-8")) if existing_vec_num else 0
existing_vec_num else 0
def add(self, data: str) -> str: def add(self, data: str) -> str:
""" """
@@ -81,20 +81,18 @@ class RedisMemory(MemoryProviderSingleton):
Returns: Message indicating that the data has been added. Returns: Message indicating that the data has been added.
""" """
if 'Command Error:' in data: if "Command Error:" in data:
return "" return ""
vector = get_ada_embedding(data) vector = get_ada_embedding(data)
vector = np.array(vector).astype(np.float32).tobytes() vector = np.array(vector).astype(np.float32).tobytes()
data_dict = { data_dict = {b"data": data, "embedding": vector}
b"data": data,
"embedding": vector
}
pipe = self.redis.pipeline() pipe = self.redis.pipeline()
pipe.hset(f"{self.cfg.memory_index}:{self.vec_num}", mapping=data_dict) pipe.hset(f"{self.cfg.memory_index}:{self.vec_num}", mapping=data_dict)
_text = f"Inserting data into memory at index: {self.vec_num}:\n"\ _text = (
f"data: {data}" f"Inserting data into memory at index: {self.vec_num}:\n" f"data: {data}"
)
self.vec_num += 1 self.vec_num += 1
pipe.set(f'{self.cfg.memory_index}-vec_num', self.vec_num) pipe.set(f"{self.cfg.memory_index}-vec_num", self.vec_num)
pipe.execute() pipe.execute()
return _text return _text
@@ -118,11 +116,7 @@ class RedisMemory(MemoryProviderSingleton):
self.redis.flushall() self.redis.flushall()
return "Obliviated" return "Obliviated"
def get_relevant( def get_relevant(self, data: str, num_relevant: int = 5) -> Optional[List[Any]]:
self,
data: str,
num_relevant: int = 5
) -> Optional[List[Any]]:
""" """
Returns all the data in the memory that is relevant to the given data. Returns all the data in the memory that is relevant to the given data.
Args: Args:
@@ -133,10 +127,12 @@ class RedisMemory(MemoryProviderSingleton):
""" """
query_embedding = get_ada_embedding(data) query_embedding = get_ada_embedding(data)
base_query = f"*=>[KNN {num_relevant} @embedding $vector AS vector_score]" base_query = f"*=>[KNN {num_relevant} @embedding $vector AS vector_score]"
query = Query(base_query).return_fields( query = (
"data", Query(base_query)
"vector_score" .return_fields("data", "vector_score")
).sort_by("vector_score").dialect(2) .sort_by("vector_score")
.dialect(2)
)
query_vector = np.array(query_embedding).astype(np.float32).tobytes() query_vector = np.array(query_embedding).astype(np.float32).tobytes()
try: try:

View File

@@ -1,5 +1,5 @@
from config import Config from autogpt.config import Config
from memory.base import MemoryProviderSingleton, get_ada_embedding from autogpt.memory.base import MemoryProviderSingleton, get_ada_embedding
import uuid import uuid
import weaviate import weaviate
from weaviate import Client from weaviate import Client

108
autogpt/prompt.py Normal file
View File

@@ -0,0 +1,108 @@
from autogpt.promptgenerator import PromptGenerator
def get_prompt() -> str:
"""
This function generates a prompt string that includes various constraints,
commands, resources, and performance evaluations.
Returns:
str: The generated prompt string.
"""
# Initialize the PromptGenerator object
prompt_generator = PromptGenerator()
# Add constraints to the PromptGenerator object
prompt_generator.add_constraint(
"~4000 word limit for short term memory. Your short term memory is short, so"
" immediately save important information to files."
)
prompt_generator.add_constraint(
"If you are unsure how you previously did something or want to recall past"
" events, thinking about similar events will help you remember."
)
prompt_generator.add_constraint("No user assistance")
prompt_generator.add_constraint(
'Exclusively use the commands listed in double quotes e.g. "command name"'
)
# Define the command list
commands = [
("Google Search", "google", {"input": "<search>"}),
(
"Browse Website",
"browse_website",
{"url": "<url>", "question": "<what_you_want_to_find_on_website>"},
),
(
"Start GPT Agent",
"start_agent",
{"name": "<name>", "task": "<short_task_desc>", "prompt": "<prompt>"},
),
(
"Message GPT Agent",
"message_agent",
{"key": "<key>", "message": "<message>"},
),
("List GPT Agents", "list_agents", {}),
("Delete GPT Agent", "delete_agent", {"key": "<key>"}),
("Write to file", "write_to_file", {"file": "<file>", "text": "<text>"}),
("Read file", "read_file", {"file": "<file>"}),
("Append to file", "append_to_file", {"file": "<file>", "text": "<text>"}),
("Delete file", "delete_file", {"file": "<file>"}),
("Search Files", "search_files", {"directory": "<directory>"}),
("Evaluate Code", "evaluate_code", {"code": "<full_code_string>"}),
(
"Get Improved Code",
"improve_code",
{"suggestions": "<list_of_suggestions>", "code": "<full_code_string>"},
),
(
"Write Tests",
"write_tests",
{"code": "<full_code_string>", "focus": "<list_of_focus_areas>"},
),
("Execute Python File", "execute_python_file", {"file": "<file>"}),
(
"Execute Shell Command, non-interactive commands only",
"execute_shell",
{"command_line": "<command_line>"},
),
("Task Complete (Shutdown)", "task_complete", {"reason": "<reason>"}),
("Generate Image", "generate_image", {"prompt": "<prompt>"}),
("Do Nothing", "do_nothing", {}),
]
# Add commands to the PromptGenerator object
for command_label, command_name, args in commands:
prompt_generator.add_command(command_label, command_name, args)
# Add resources to the PromptGenerator object
prompt_generator.add_resource(
"Internet access for searches and information gathering."
)
prompt_generator.add_resource("Long Term memory management.")
prompt_generator.add_resource(
"GPT-3.5 powered Agents for delegation of simple tasks."
)
prompt_generator.add_resource("File output.")
# Add performance evaluations to the PromptGenerator object
prompt_generator.add_performance_evaluation(
"Continuously review and analyze your actions to ensure you are performing to"
" the best of your abilities."
)
prompt_generator.add_performance_evaluation(
"Constructively self-criticize your big-picture behavior constantly."
)
prompt_generator.add_performance_evaluation(
"Reflect on past decisions and strategies to refine your approach."
)
prompt_generator.add_performance_evaluation(
"Every command has a cost, so be smart and efficient. Aim to complete tasks in"
" the least number of steps."
)
# Generate the prompt string
return prompt_generator.generate_prompt_string()

134
autogpt/promptgenerator.py Normal file
View File

@@ -0,0 +1,134 @@
import json
class PromptGenerator:
"""
A class for generating custom prompt strings based on constraints, commands,
resources, and performance evaluations.
"""
def __init__(self):
"""
Initialize the PromptGenerator object with empty lists of constraints,
commands, resources, and performance evaluations.
"""
self.constraints = []
self.commands = []
self.resources = []
self.performance_evaluation = []
self.response_format = {
"thoughts": {
"text": "thought",
"reasoning": "reasoning",
"plan": "- short bulleted\n- list that conveys\n- long-term plan",
"criticism": "constructive self-criticism",
"speak": "thoughts summary to say to user",
},
"command": {"name": "command name", "args": {"arg name": "value"}},
}
def add_constraint(self, constraint):
"""
Add a constraint to the constraints list.
Args:
constraint (str): The constraint to be added.
"""
self.constraints.append(constraint)
def add_command(self, command_label, command_name, args=None):
"""
Add a command to the commands list with a label, name, and optional arguments.
Args:
command_label (str): The label of the command.
command_name (str): The name of the command.
args (dict, optional): A dictionary containing argument names and their
values. Defaults to None.
"""
if args is None:
args = {}
command_args = {arg_key: arg_value for arg_key, arg_value in args.items()}
command = {
"label": command_label,
"name": command_name,
"args": command_args,
}
self.commands.append(command)
def _generate_command_string(self, command):
"""
Generate a formatted string representation of a command.
Args:
command (dict): A dictionary containing command information.
Returns:
str: The formatted command string.
"""
args_string = ", ".join(
f'"{key}": "{value}"' for key, value in command["args"].items()
)
return f'{command["label"]}: "{command["name"]}", args: {args_string}'
def add_resource(self, resource: str) -> None:
"""
Add a resource to the resources list.
Args:
resource (str): The resource to be added.
"""
self.resources.append(resource)
def add_performance_evaluation(self, evaluation: str) -> None:
"""
Add a performance evaluation item to the performance_evaluation list.
Args:
evaluation (str): The evaluation item to be added.
"""
self.performance_evaluation.append(evaluation)
def _generate_numbered_list(self, items, item_type="list") -> str:
"""
Generate a numbered list from given items based on the item_type.
Args:
items (list): A list of items to be numbered.
item_type (str, optional): The type of items in the list.
Defaults to 'list'.
Returns:
str: The formatted numbered list.
"""
if item_type == "command":
return "\n".join(
f"{i+1}. {self._generate_command_string(item)}"
for i, item in enumerate(items)
)
else:
return "\n".join(f"{i+1}. {item}" for i, item in enumerate(items))
def generate_prompt_string(self) -> str:
"""
Generate a prompt string based on the constraints, commands, resources,
and performance evaluations.
Returns:
str: The generated prompt string.
"""
formatted_response_format = json.dumps(self.response_format, indent=4)
return (
f"Constraints:\n{self._generate_numbered_list(self.constraints)}\n\n"
"Commands:\n"
f"{self._generate_numbered_list(self.commands, item_type='command')}\n\n"
f"Resources:\n{self._generate_numbered_list(self.resources)}\n\n"
"Performance Evaluation:\n"
f"{self._generate_numbered_list(self.performance_evaluation)}\n\n"
"You should only respond in JSON format as described below \nResponse"
f" Format: \n{formatted_response_format} \nEnsure the response can be"
"parsed by Python json.loads"
)

View File

@@ -1,12 +1,17 @@
import os import os
from playsound import playsound
import requests import requests
from config import Config from playsound import playsound
cfg = Config()
import gtts from autogpt.config import Config
import threading import threading
from threading import Lock, Semaphore from threading import Lock, Semaphore
import gtts
cfg = Config()
# Default voice IDs # Default voice IDs
default_voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"] default_voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"]
@@ -19,26 +24,29 @@ placeholders = {"your-voice-id"}
# Use custom voice IDs if provided and not placeholders, otherwise use default voice IDs # Use custom voice IDs if provided and not placeholders, otherwise use default voice IDs
voices = [ voices = [
custom_voice_1 if custom_voice_1 and custom_voice_1 not in placeholders else default_voices[0], custom_voice_1
custom_voice_2 if custom_voice_2 and custom_voice_2 not in placeholders else default_voices[1] if custom_voice_1 and custom_voice_1 not in placeholders
else default_voices[0],
custom_voice_2
if custom_voice_2 and custom_voice_2 not in placeholders
else default_voices[1],
] ]
tts_headers = { tts_headers = {"Content-Type": "application/json", "xi-api-key": cfg.elevenlabs_api_key}
"Content-Type": "application/json",
"xi-api-key": cfg.elevenlabs_api_key
}
mutex_lock = Lock() # Ensure only one sound is played at a time mutex_lock = Lock() # Ensure only one sound is played at a time
queue_semaphore = Semaphore(1) # The amount of sounds to queue before blocking the main thread queue_semaphore = Semaphore(
1
) # The amount of sounds to queue before blocking the main thread
def eleven_labs_speech(text, voice_index=0): def eleven_labs_speech(text, voice_index=0):
"""Speak text using elevenlabs.io's API""" """Speak text using elevenlabs.io's API"""
tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format( tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format(
voice_id=voices[voice_index]) voice_id=voices[voice_index]
)
formatted_message = {"text": text} formatted_message = {"text": text}
response = requests.post( response = requests.post(tts_url, headers=tts_headers, json=formatted_message)
tts_url, headers=tts_headers, json=formatted_message)
if response.status_code == 200: if response.status_code == 200:
with mutex_lock: with mutex_lock:
@@ -53,6 +61,24 @@ def eleven_labs_speech(text, voice_index=0):
return False return False
def brian_speech(text):
"""Speak text using Brian with the streamelements API"""
tts_url = f"https://api.streamelements.com/kappa/v2/speech?voice=Brian&text={text}"
response = requests.get(tts_url)
if response.status_code == 200:
with mutex_lock:
with open("speech.mp3", "wb") as f:
f.write(response.content)
playsound("speech.mp3")
os.remove("speech.mp3")
return True
else:
print("Request failed with status code:", response.status_code)
print("Response content:", response.content)
return False
def gtts_speech(text): def gtts_speech(text):
tts = gtts.gTTS(text) tts = gtts.gTTS(text)
with mutex_lock: with mutex_lock:
@@ -72,11 +98,14 @@ def macos_tts_speech(text, voice_index=0):
def say_text(text, voice_index=0): def say_text(text, voice_index=0):
def speak(): def speak():
if not cfg.elevenlabs_api_key: if not cfg.elevenlabs_api_key:
if cfg.use_mac_os_tts == 'True': if cfg.use_mac_os_tts == "True":
macos_tts_speech(text, voice_index) macos_tts_speech(text)
elif cfg.use_brian_tts == "True":
success = brian_speech(text)
if not success:
gtts_speech(text)
else: else:
gtts_speech(text) gtts_speech(text)
else: else:

View File

@@ -1,14 +1,15 @@
import itertools
import sys import sys
import threading import threading
import itertools
import time import time
class Spinner: class Spinner:
"""A simple spinner class""" """A simple spinner class"""
def __init__(self, message="Loading...", delay=0.1): def __init__(self, message="Loading...", delay=0.1):
"""Initialize the spinner class""" """Initialize the spinner class"""
self.spinner = itertools.cycle(['-', '/', '|', '\\']) self.spinner = itertools.cycle(["-", "/", "|", "\\"])
self.delay = delay self.delay = delay
self.message = message self.message = message
self.running = False self.running = False
@@ -17,10 +18,10 @@ class Spinner:
def spin(self): def spin(self):
"""Spin the spinner""" """Spin the spinner"""
while self.running: while self.running:
sys.stdout.write(next(self.spinner) + " " + self.message + "\r") sys.stdout.write(f"{next(self.spinner)} {self.message}\r")
sys.stdout.flush() sys.stdout.flush()
time.sleep(self.delay) time.sleep(self.delay)
sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r') sys.stdout.write(f"\r{' ' * (len(self.message) + 2)}\r")
def __enter__(self): def __enter__(self):
"""Start the spinner""" """Start the spinner"""
@@ -31,6 +32,7 @@ class Spinner:
def __exit__(self, exc_type, exc_value, exc_traceback): def __exit__(self, exc_type, exc_value, exc_traceback):
"""Stop the spinner""" """Stop the spinner"""
self.running = False self.running = False
self.spinner_thread.join() if self.spinner_thread is not None:
sys.stdout.write('\r' + ' ' * (len(self.message) + 2) + '\r') self.spinner_thread.join()
sys.stdout.write(f"\r{' ' * (len(self.message) + 2)}\r")
sys.stdout.flush() sys.stdout.flush()

69
autogpt/summary.py Normal file
View File

@@ -0,0 +1,69 @@
from autogpt.llm_utils import create_chat_completion
def summarize_text(driver, text, question):
if not text:
return "Error: No text to summarize"
text_length = len(text)
print(f"Text length: {text_length} characters")
summaries = []
chunks = list(split_text(text))
scroll_ratio = 1 / len(chunks)
for i, chunk in enumerate(chunks):
scroll_to_percentage(driver, scroll_ratio * i)
print(f"Summarizing chunk {i + 1} / {len(chunks)}")
messages = [create_message(chunk, question)]
summary = create_chat_completion(
model="gpt-3.5-turbo",
messages=messages,
max_tokens=300,
)
summaries.append(summary)
print(f"Summarized {len(chunks)} chunks.")
combined_summary = "\n".join(summaries)
messages = [create_message(combined_summary, question)]
return create_chat_completion(
model="gpt-3.5-turbo",
messages=messages,
max_tokens=300,
)
def split_text(text, max_length=8192):
paragraphs = text.split("\n")
current_length = 0
current_chunk = []
for paragraph in paragraphs:
if current_length + len(paragraph) + 1 <= max_length:
current_chunk.append(paragraph)
current_length += len(paragraph) + 1
else:
yield "\n".join(current_chunk)
current_chunk = [paragraph]
current_length = len(paragraph) + 1
if current_chunk:
yield "\n".join(current_chunk)
def create_message(chunk, question):
return {
"role": "user",
"content": f'"""{chunk}""" Using the above text, please answer the following'
f' question: "{question}" -- if the question cannot be answered using the text,'
" please summarize the text.",
}
def scroll_to_percentage(driver, ratio):
if ratio < 0 or ratio > 1:
raise ValueError("Percentage should be between 0 and 1")
driver.execute_script(f"window.scrollTo(0, document.body.scrollHeight * {ratio});")

View File

@@ -1,14 +1,21 @@
from typing import Dict, List
import tiktoken import tiktoken
from typing import List, Dict
from autogpt.logger import logger
def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5-turbo-0301") -> int: 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. Returns the number of tokens used by a list of messages.
Args: Args:
messages (list): A list of messages, each of which is a dictionary containing the role and content of the message. messages (list): A list of messages, each of which is a dictionary
model (str): The name of the model to use for tokenization. Defaults to "gpt-3.5-turbo-0301". containing the role and content of the message.
model (str): The name of the model to use for tokenization.
Defaults to "gpt-3.5-turbo-0301".
Returns: Returns:
int: The number of tokens used by the list of messages. int: The number of tokens used by the list of messages.
@@ -19,19 +26,26 @@ def count_message_tokens(messages : List[Dict[str, str]], model : str = "gpt-3.5
logger.warn("Warning: model not found. Using cl100k_base encoding.") logger.warn("Warning: model not found. Using cl100k_base encoding.")
encoding = tiktoken.get_encoding("cl100k_base") encoding = tiktoken.get_encoding("cl100k_base")
if model == "gpt-3.5-turbo": if model == "gpt-3.5-turbo":
# !Node: gpt-3.5-turbo may change over time. Returning num tokens assuming gpt-3.5-turbo-0301.") # !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") return count_message_tokens(messages, model="gpt-3.5-turbo-0301")
elif model == "gpt-4": elif model == "gpt-4":
# !Note: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.") # !Note: gpt-4 may change over time. Returning num tokens assuming gpt-4-0314.")
return count_message_tokens(messages, model="gpt-4-0314") return count_message_tokens(messages, model="gpt-4-0314")
elif model == "gpt-3.5-turbo-0301": elif model == "gpt-3.5-turbo-0301":
tokens_per_message = 4 # every message follows <|start|>{role/name}\n{content}<|end|>\n tokens_per_message = (
4 # every message follows <|start|>{role/name}\n{content}<|end|>\n
)
tokens_per_name = -1 # if there's a name, the role is omitted tokens_per_name = -1 # if there's a name, the role is omitted
elif model == "gpt-4-0314": elif model == "gpt-4-0314":
tokens_per_message = 3 tokens_per_message = 3
tokens_per_name = 1 tokens_per_name = 1
else: 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.""") raise NotImplementedError(
f"num_tokens_from_messages() is not implemented for model {model}.\n"
" See https://github.com/openai/openai-python/blob/main/chatml.md for"
" information on how messages are converted to tokens."
)
num_tokens = 0 num_tokens = 0
for message in messages: for message in messages:
num_tokens += tokens_per_message num_tokens += tokens_per_message
@@ -55,5 +69,4 @@ def count_string_tokens(string: str, model_name: str) -> int:
int: The number of tokens in the text string. int: The number of tokens in the text string.
""" """
encoding = tiktoken.encoding_for_model(model_name) encoding = tiktoken.encoding_for_model(model_name)
num_tokens = len(encoding.encode(string)) return len(encoding.encode(string))
return num_tokens

26
autogpt/utils.py Normal file
View File

@@ -0,0 +1,26 @@
import yaml
from colorama import Fore
def clean_input(prompt: str = ""):
try:
return input(prompt)
except KeyboardInterrupt:
print("You interrupted Auto-GPT")
print("Quitting...")
exit(0)
def validate_yaml_file(file: str):
try:
with open(file, encoding="utf-8") as fp:
yaml.load(fp.read(), Loader=yaml.FullLoader)
except FileNotFoundError:
return (False, f"The file {Fore.CYAN}`{file}`{Fore.RESET} wasn't found")
except yaml.YAMLError as e:
return (
False,
f"There was an issue while trying to read with your AI Settings file: {e}",
)
return (True, f"Successfully validated {Fore.CYAN}`{file}`{Fore.RESET}!")

85
autogpt/web.py Normal file
View File

@@ -0,0 +1,85 @@
from selenium import webdriver
import autogpt.summary as summary
from bs4 import BeautifulSoup
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.chrome.options import Options
import logging
from pathlib import Path
from autogpt.config import Config
file_dir = Path(__file__).parent
cfg = Config()
def browse_website(url, question):
driver, text = scrape_text_with_selenium(url)
add_header(driver)
summary_text = summary.summarize_text(driver, text, question)
links = scrape_links_with_selenium(driver)
# Limit links to 5
if len(links) > 5:
links = links[:5]
close_browser(driver)
return f"Answer gathered from website: {summary_text} \n \n Links: {links}", driver
def scrape_text_with_selenium(url):
logging.getLogger("selenium").setLevel(logging.CRITICAL)
options = Options()
options.add_argument(
"user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.5615.49 Safari/537.36"
)
driver = webdriver.Chrome(
executable_path=ChromeDriverManager().install(), options=options
)
driver.get(url)
WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "body"))
)
# Get the HTML content directly from the browser's DOM
page_source = driver.execute_script("return document.body.outerHTML;")
soup = BeautifulSoup(page_source, "html.parser")
for script in soup(["script", "style"]):
script.extract()
text = soup.get_text()
lines = (line.strip() for line in text.splitlines())
chunks = (phrase.strip() for line in lines for phrase in line.split(" "))
text = "\n".join(chunk for chunk in chunks if chunk)
return driver, text
def scrape_links_with_selenium(driver):
page_source = driver.page_source
soup = BeautifulSoup(page_source, "html.parser")
for script in soup(["script", "style"]):
script.extract()
hyperlinks = extract_hyperlinks(soup)
return format_hyperlinks(hyperlinks)
def close_browser(driver):
driver.quit()
def extract_hyperlinks(soup):
return [(link.text, link["href"]) for link in soup.find_all("a", href=True)]
def format_hyperlinks(hyperlinks):
return [f"{link_text} ({link_url})" for link_text, link_url in hyperlinks]
def add_header(driver):
driver.execute_script(open(f"{file_dir}/js/overlay.js", "r").read())

16
docker-compose.yml Normal file
View File

@@ -0,0 +1,16 @@
# To boot the app run the following:
# docker-compose run auto-gpt
version: "3.9"
services:
auto-gpt:
depends_on:
- redis
build: ./
volumes:
- "./autogpt:/app"
- ".env:/app/.env"
profiles: ["exclude-from-up"]
redis:
image: "redis/redis-stack-server:latest"

View File

@@ -1 +1 @@
from scripts.main import main from autogpt import main

11
pyproject.toml Normal file
View File

@@ -0,0 +1,11 @@
[project]
name = "auto-gpt"
version = "0.1.0"
description = "A GPT based ai agent"
readme = "README.md"
[tool.black]
line-length = 88
target-version = ['py310']
include = '\.pyi?$'
extend-exclude = ""

View File

@@ -16,6 +16,12 @@ redis
orjson orjson
Pillow Pillow
weaviate-client==3.15.5 weaviate-client==3.15.5
selenium
webdriver-manager
coverage coverage
flake8 flake8
numpy numpy
pre-commit
black
sourcery
isort

View File

@@ -1,19 +0,0 @@
import os
from pathlib import Path
def load_prompt():
"""Load the prompt from data/prompt.txt"""
try:
# get directory of this file:
file_dir = Path(__file__).parent
prompt_file_path = file_dir / "data" / "prompt.txt"
# Load the prompt from data/prompt.txt
with open(prompt_file_path, "r") as prompt_file:
prompt = prompt_file.read()
return prompt
except FileNotFoundError:
print("Error: Prompt file not found", flush=True)
return ""

View File

@@ -1,64 +0,0 @@
CONSTRAINTS:
1. ~4000 word limit for short term memory. Your short term memory is short, so immediately save important information to files.
2. If you are unsure how you previously did something or want to recall past events, thinking about similar events will help you remember.
3. No user assistance
4. Exclusively use the commands listed in double quotes e.g. "command name"
COMMANDS:
1. Google Search: "google", args: "input": "<search>"
5. Browse Website: "browse_website", args: "url": "<url>", "question": "<what_you_want_to_find_on_website>"
6. Start GPT Agent: "start_agent", args: "name": "<name>", "task": "<short_task_desc>", "prompt": "<prompt>"
7. Message GPT Agent: "message_agent", args: "key": "<key>", "message": "<message>"
8. List GPT Agents: "list_agents", args: ""
9. Delete GPT Agent: "delete_agent", args: "key": "<key>"
10. Write to file: "write_to_file", args: "file": "<file>", "text": "<text>"
11. Read file: "read_file", args: "file": "<file>"
12. Append to file: "append_to_file", args: "file": "<file>", "text": "<text>"
13. Delete file: "delete_file", args: "file": "<file>"
14. Search Files: "search_files", args: "directory": "<directory>"
15. Evaluate Code: "evaluate_code", args: "code": "<full_code_string>"
16. Get Improved Code: "improve_code", args: "suggestions": "<list_of_suggestions>", "code": "<full_code_string>"
17. Write Tests: "write_tests", args: "code": "<full_code_string>", "focus": "<list_of_focus_areas>"
18. Execute Python File: "execute_python_file", args: "file": "<file>"
19. Execute Shell Command, non-interactive commands only: "execute_shell", args: "command_line": "<command_line>".
20. Task Complete (Shutdown): "task_complete", args: "reason": "<reason>"
21. Generate Image: "generate_image", args: "prompt": "<prompt>"
22. Do Nothing: "do_nothing", args: ""
RESOURCES:
1. Internet access for searches and information gathering.
2. Long Term memory management.
3. GPT-3.5 powered Agents for delegation of simple tasks.
4. File output.
PERFORMANCE EVALUATION:
1. Continuously review and analyze your actions to ensure you are performing to the best of your abilities.
2. Constructively self-criticize your big-picture behavior constantly.
3. Reflect on past decisions and strategies to refine your approach.
4. Every command has a cost, so be smart and efficient. Aim to complete tasks in the least number of steps.
You should only respond in JSON format as described below
RESPONSE FORMAT:
{
"thoughts":
{
"text": "thought",
"reasoning": "reasoning",
"plan": "- short bulleted\n- list that conveys\n- long-term plan",
"criticism": "constructive self-criticism",
"speak": "thoughts summary to say to user"
},
"command": {
"name": "command name",
"args":{
"arg name": "value"
}
}
}
Ensure the response can be parsed by Python json.loads

View File

@@ -1,88 +0,0 @@
import docker
import os
import subprocess
WORKSPACE_FOLDER = "auto_gpt_workspace"
def execute_python_file(file):
"""Execute a Python file in a Docker container and return the output"""
print (f"Executing file '{file}' in workspace '{WORKSPACE_FOLDER}'")
if not file.endswith(".py"):
return "Error: Invalid file type. Only .py files are allowed."
file_path = os.path.join(WORKSPACE_FOLDER, file)
if not os.path.isfile(file_path):
return f"Error: File '{file}' does not exist."
try:
client = docker.from_env()
image_name = 'python:3.10'
try:
client.images.get(image_name)
print(f"Image '{image_name}' found locally")
except docker.errors.ImageNotFound:
print(f"Image '{image_name}' not found locally, pulling from Docker Hub")
# Use the low-level API to stream the pull response
low_level_client = docker.APIClient()
for line in low_level_client.pull(image_name, stream=True, decode=True):
# Print the status and progress, if available
status = line.get('status')
progress = line.get('progress')
if status and progress:
print(f"{status}: {progress}")
elif status:
print(status)
# You can replace 'python:3.8' with the desired Python image/version
# You can find available Python images on Docker Hub:
# https://hub.docker.com/_/python
container = client.containers.run(
image_name,
f'python {file}',
volumes={
os.path.abspath(WORKSPACE_FOLDER): {
'bind': '/workspace',
'mode': 'ro'}},
working_dir='/workspace',
stderr=True,
stdout=True,
detach=True,
)
output = container.wait()
logs = container.logs().decode('utf-8')
container.remove()
# print(f"Execution complete. Output: {output}")
# print(f"Logs: {logs}")
return logs
except Exception as e:
return f"Error: {str(e)}"
def execute_shell(command_line):
current_dir = os.getcwd()
if not WORKSPACE_FOLDER in current_dir: # Change dir into workspace if necessary
work_dir = os.path.join(os.getcwd(), WORKSPACE_FOLDER)
os.chdir(work_dir)
print (f"Executing command '{command_line}' in working directory '{os.getcwd()}'")
result = subprocess.run(command_line, capture_output=True, shell=True)
output = f"STDOUT:\n{result.stdout}\nSTDERR:\n{result.stderr}"
# Change back to whatever the prior working dir was
os.chdir(current_dir)
return output

View File

@@ -1,84 +0,0 @@
import os
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)
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)
if os.path.commonprefix([base, norm_new_path]) != base:
raise ValueError("Attempted to access outside of working directory.")
return norm_new_path
def read_file(filename):
"""Read a file and return the contents"""
try:
filepath = safe_join(working_directory, filename)
with open(filepath, "r", encoding='utf-8') as f:
content = f.read()
return content
except Exception as e:
return "Error: " + str(e)
def write_to_file(filename, text):
"""Write text to a file"""
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", encoding='utf-8') as f:
f.write(text)
return "File written to successfully."
except Exception as e:
return "Error: " + str(e)
def append_to_file(filename, text):
"""Append text to a file"""
try:
filepath = safe_join(working_directory, filename)
with open(filepath, "a") as f:
f.write(text)
return "Text appended successfully."
except Exception as e:
return "Error: " + str(e)
def delete_file(filename):
"""Delete a file"""
try:
filepath = safe_join(working_directory, filename)
os.remove(filepath)
return "File deleted successfully."
except Exception as e:
return "Error: " + str(e)
def search_files(directory):
found_files = []
if directory == "" or directory == "/":
search_directory = working_directory
else:
search_directory = safe_join(working_directory, directory)
for root, _, files in os.walk(search_directory):
for file in files:
if file.startswith('.'):
continue
relative_path = os.path.relpath(os.path.join(root, file), working_directory)
found_files.append(relative_path)
return found_files

View File

@@ -1,27 +0,0 @@
import openai
from config import Config
cfg = Config()
openai.api_key = cfg.openai_api_key
# Overly simple abstraction until we create something better
def create_chat_completion(messages, model=None, temperature=cfg.temperature, max_tokens=None)->str:
"""Create a chat completion using the OpenAI API"""
if cfg.use_azure:
response = openai.ChatCompletion.create(
deployment_id=cfg.get_azure_deployment_id_for_model(model),
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
else:
response = openai.ChatCompletion.create(
model=model,
messages=messages,
temperature=temperature,
max_tokens=max_tokens
)
return response.choices[0].message["content"]

View File

@@ -1,442 +1,11 @@
import json from colorama import Style, init
import random
import commands as cmd
import utils
from memory import get_memory, get_supported_memory_backends
import data
import chat
from colorama import Fore, Style
from spinner import Spinner
import time
import speak
from config import Config
from json_parser import fix_and_parse_json
from ai_config import AIConfig
import traceback
import yaml
import argparse
from logger import logger
import logging
cfg = Config() # Initialize colorama
init(autoreset=True)
# Use the bold ANSI style
def check_openai_api_key(): print(
"""Check if the OpenAI API key is set in config.py or as an environment variable.""" f"""{Style.BRIGHT}Please run:
if not cfg.openai_api_key: python -m autogpt
print( """
Fore.RED + )
"Please set your OpenAI API key in .env or as an environment variable."
)
print("You can get your key from https://beta.openai.com/account/api-keys")
exit(1)
def attempt_to_fix_json_by_finding_outermost_brackets(json_string):
if cfg.speak_mode and cfg.debug_mode:
speak.say_text("I have received an invalid JSON response from the OpenAI API. Trying to fix it now.")
logger.typewriter_log("Attempting to fix JSON by finding outermost brackets\n")
try:
# Use regex to search for JSON objects
import regex
json_pattern = regex.compile(r"\{(?:[^{}]|(?R))*\}")
json_match = json_pattern.search(json_string)
if json_match:
# Extract the valid JSON object from the string
json_string = json_match.group(0)
logger.typewriter_log(title="Apparently json was fixed.", title_color=Fore.GREEN)
if cfg.speak_mode and cfg.debug_mode:
speak.say_text("Apparently json was fixed.")
else:
raise ValueError("No valid JSON object found")
except (json.JSONDecodeError, ValueError) as e:
if cfg.speak_mode:
speak.say_text("Didn't work. I will have to ignore this response then.")
logger.error("Error: Invalid JSON, setting it to empty JSON now.\n")
json_string = {}
return json_string
def print_assistant_thoughts(assistant_reply):
"""Prints the assistant's thoughts to the console"""
global ai_name
global cfg
try:
try:
# Parse and print Assistant response
assistant_reply_json = fix_and_parse_json(assistant_reply)
except json.JSONDecodeError as e:
logger.error("Error: Invalid JSON in assistant thoughts\n", assistant_reply)
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets(assistant_reply)
assistant_reply_json = fix_and_parse_json(assistant_reply_json)
# Check if assistant_reply_json is a string and attempt to parse it into a JSON object
if isinstance(assistant_reply_json, str):
try:
assistant_reply_json = json.loads(assistant_reply_json)
except json.JSONDecodeError as e:
logger.error("Error: Invalid JSON\n", assistant_reply)
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets(assistant_reply_json)
assistant_thoughts_reasoning = None
assistant_thoughts_plan = None
assistant_thoughts_speak = None
assistant_thoughts_criticism = None
assistant_thoughts = assistant_reply_json.get("thoughts", {})
assistant_thoughts_text = assistant_thoughts.get("text")
if assistant_thoughts:
assistant_thoughts_reasoning = assistant_thoughts.get("reasoning")
assistant_thoughts_plan = assistant_thoughts.get("plan")
assistant_thoughts_criticism = assistant_thoughts.get("criticism")
assistant_thoughts_speak = assistant_thoughts.get("speak")
logger.typewriter_log(f"{ai_name.upper()} THOUGHTS:", Fore.YELLOW, assistant_thoughts_text)
logger.typewriter_log("REASONING:", Fore.YELLOW, assistant_thoughts_reasoning)
if assistant_thoughts_plan:
logger.typewriter_log("PLAN:", Fore.YELLOW, "")
# If it's a list, join it into a string
if isinstance(assistant_thoughts_plan, list):
assistant_thoughts_plan = "\n".join(assistant_thoughts_plan)
elif isinstance(assistant_thoughts_plan, dict):
assistant_thoughts_plan = str(assistant_thoughts_plan)
# Split the input_string using the newline character and dashes
lines = assistant_thoughts_plan.split('\n')
for line in lines:
line = line.lstrip("- ")
logger.typewriter_log("- ", Fore.GREEN, line.strip())
logger.typewriter_log("CRITICISM:", Fore.YELLOW, assistant_thoughts_criticism)
# Speak the assistant's thoughts
if cfg.speak_mode and assistant_thoughts_speak:
speak.say_text(assistant_thoughts_speak)
return assistant_reply_json
except json.decoder.JSONDecodeError as e:
logger.error("Error: Invalid JSON\n", assistant_reply)
if cfg.speak_mode:
speak.say_text("I have received an invalid JSON response from the OpenAI API. I cannot ignore this response.")
# All other errors, return "Error: + error message"
except Exception as e:
call_stack = traceback.format_exc()
logger.error("Error: \n", call_stack)
def load_variables(config_file="config.yaml"):
"""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)
ai_name = config.get("ai_name")
ai_role = config.get("ai_role")
ai_goals = config.get("ai_goals")
except FileNotFoundError:
ai_name = ""
ai_role = ""
ai_goals = []
# Prompt the user for input if config file is missing or empty values
if not ai_name:
ai_name = utils.clean_input("Name your AI: ")
if ai_name == "":
ai_name = "Entrepreneur-GPT"
if not ai_role:
ai_role = utils.clean_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."
if not ai_goals:
print("Enter up to 5 goals for your AI: ")
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 = utils.clean_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)
prompt = data.load_prompt()
prompt_start = """Your decisions must always be made independently without seeking user assistance. Play to your strengths as a LLM and pursue simple strategies with no legal complications."""
# 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"
full_prompt += f"\n\n{prompt}"
return full_prompt
def construct_prompt():
"""Construct the prompt for the AI to respond to"""
config = AIConfig.load()
if config.ai_name:
logger.typewriter_log(
f"Welcome back! ",
Fore.GREEN,
f"Would you like me to return to being {config.ai_name}?",
speak_text=True)
should_continue = utils.clean_input(f"""Continue with the last settings?
Name: {config.ai_name}
Role: {config.ai_role}
Goals: {config.ai_goals}
Continue (y/n): """)
if should_continue.lower() == "n":
config = AIConfig()
if not config.ai_name:
config = prompt_user()
config.save()
# Get rid of this global:
global ai_name
ai_name = config.ai_name
full_prompt = config.construct_full_prompt()
return full_prompt
def prompt_user():
"""Prompt the user for input"""
ai_name = ""
# Construct the prompt
logger.typewriter_log(
"Welcome to Auto-GPT! ",
Fore.GREEN,
"Enter the name of your AI and its role below. Entering nothing will load defaults.",
speak_text=True)
# Get AI Name from User
logger.typewriter_log(
"Name your AI: ",
Fore.GREEN,
"For example, 'Entrepreneur-GPT'")
ai_name = utils.clean_input("AI Name: ")
if ai_name == "":
ai_name = "Entrepreneur-GPT"
logger.typewriter_log(
f"{ai_name} here!",
Fore.LIGHTBLUE_EX,
"I am at your service.",
speak_text=True)
# Get AI Role from User
logger.typewriter_log(
"Describe your AI's role: ",
Fore.GREEN,
"For example, 'an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth.'")
ai_role = utils.clean_input(f"{ai_name} is: ")
if ai_role == "":
ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth."
# Enter up to 5 goals for the AI
logger.typewriter_log(
"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 = utils.clean_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"]
config = AIConfig(ai_name, ai_role, ai_goals)
return config
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)
parser = argparse.ArgumentParser(description='Process arguments.')
parser.add_argument('--continuous', action='store_true', help='Enable Continuous Mode')
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')
parser.add_argument('--use-memory', '-m', dest="memory_type", help='Defines which Memory backend to use')
args = parser.parse_args()
if args.debug:
logger.typewriter_log("Debug Mode: ", Fore.GREEN, "ENABLED")
cfg.set_debug_mode(True)
if args.continuous:
logger.typewriter_log("Continuous Mode: ", Fore.RED, "ENABLED")
logger.typewriter_log(
"WARNING: ",
Fore.RED,
"Continuous mode is not recommended. It is potentially dangerous and may cause your AI to run forever or carry out actions you would not usually authorise. Use at your own risk.")
cfg.set_continuous_mode(True)
if args.speak:
logger.typewriter_log("Speak Mode: ", Fore.GREEN, "ENABLED")
cfg.set_speak_mode(True)
if args.gpt3only:
logger.typewriter_log("GPT3.5 Only Mode: ", Fore.GREEN, "ENABLED")
cfg.set_smart_llm_model(cfg.fast_llm_model)
if args.gpt4only:
logger.typewriter_log("GPT4 Only Mode: ", Fore.GREEN, "ENABLED")
cfg.set_fast_llm_model(cfg.smart_llm_model)
if args.debug:
logger.typewriter_log("Debug Mode: ", Fore.GREEN, "ENABLED")
cfg.set_debug_mode(True)
if args.memory_type:
supported_memory = get_supported_memory_backends()
chosen = args.memory_type
if not chosen in supported_memory:
logger.typewriter_log("ONLY THE FOLLOWING MEMORY BACKENDS ARE SUPPORTED: ", Fore.RED, f'{supported_memory}')
logger.typewriter_log(f"Defaulting to: ", Fore.YELLOW, cfg.memory_backend)
else:
cfg.memory_backend = chosen
def main():
global ai_name, memory
# TODO: fill in llm values here
check_openai_api_key()
parse_arguments()
logger.set_level(logging.DEBUG if cfg.debug_mode else logging.INFO)
ai_name = ""
prompt = construct_prompt()
# print(prompt)
# Initialize variables
full_message_history = []
result = None
next_action_count = 0
# Make a constant:
user_input = "Determine which next command to use, and respond using the format specified above:"
# Initialize memory and make sure it is empty.
# this is particularly important for indexing and referencing pinecone memory
memory = get_memory(cfg, init=True)
print('Using memory of type: ' + memory.__class__.__name__)
# Interaction Loop
while True:
# Send message to AI, get response
with Spinner("Thinking... "):
assistant_reply = chat.chat_with_ai(
prompt,
user_input,
full_message_history,
memory,
cfg.fast_token_limit) # TODO: This hardcodes the model to use GPT3.5. Make this an argument
# Print Assistant thoughts
print_assistant_thoughts(assistant_reply)
# Get command name and arguments
try:
command_name, arguments = cmd.get_command(
attempt_to_fix_json_by_finding_outermost_brackets(assistant_reply))
if cfg.speak_mode:
speak.say_text(f"I want to execute {command_name}")
except Exception as e:
logger.error("Error: \n", str(e))
if not cfg.continuous_mode and next_action_count == 0:
### GET USER AUTHORIZATION TO EXECUTE COMMAND ###
# Get key press: Prompt the user to press enter to continue or escape
# to exit
user_input = ""
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}")
print(
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 = utils.clean_input(Fore.MAGENTA + "Input:" + Style.RESET_ALL)
if console_input.lower().rstrip() == "y":
user_input = "GENERATE NEXT COMMAND JSON"
break
elif console_input.lower().startswith("y -"):
try:
next_action_count = abs(int(console_input.split(" ")[1]))
user_input = "GENERATE NEXT COMMAND JSON"
except ValueError:
print("Invalid input format. Please enter 'y -n' where n is the number of continuous tasks.")
continue
break
elif console_input.lower() == "n":
user_input = "EXIT"
break
else:
user_input = console_input
command_name = "human_feedback"
break
if user_input == "GENERATE NEXT COMMAND JSON":
logger.typewriter_log(
"-=-=-=-=-=-=-= COMMAND AUTHORISED BY USER -=-=-=-=-=-=-=",
Fore.MAGENTA,
"")
elif user_input == "EXIT":
print("Exiting...", flush=True)
break
else:
# Print command
logger.typewriter_log(
"NEXT ACTION: ",
Fore.CYAN,
f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL} ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}")
# Execute command
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}"
else:
result = f"Command {command_name} returned: {cmd.execute_command(command_name, arguments)}"
if next_action_count > 0:
next_action_count -= 1
memory_to_add = f"Assistant Reply: {assistant_reply} " \
f"\nResult: {result} " \
f"\nHuman Feedback: {user_input} "
memory.add(memory_to_add)
# Check if there's a result from the command append it to the message
# history
if result is not None:
full_message_history.append(chat.create_chat_message("system", result))
logger.typewriter_log("SYSTEM: ", Fore.YELLOW, result)
else:
full_message_history.append(
chat.create_chat_message(
"system", "Unable to execute command"))
logger.typewriter_log("SYSTEM: ", Fore.YELLOW, "Unable to execute command")
if __name__ == "__main__":
main()

View File

@@ -1,8 +0,0 @@
def clean_input(prompt: str=''):
try:
return input(prompt)
except KeyboardInterrupt:
print("You interrupted Auto-GPT")
print("Quitting...")
exit(0)

View File

@@ -1,8 +1,8 @@
import unittest import unittest
if __name__ == "__main__": if __name__ == "__main__":
# Load all tests from the 'scripts/tests' package # Load all tests from the 'autogpt/tests' package
suite = unittest.defaultTestLoader.discover('scripts/tests') suite = unittest.defaultTestLoader.discover("autogpt/tests")
# Run the tests # Run the tests
unittest.TextTestRunner().run(suite) unittest.TextTestRunner().run(suite)

View File

@@ -1,5 +1,6 @@
import sys
import os import os
import sys
sys.path.insert(0, os.path.abspath( sys.path.insert(
os.path.join(os.path.dirname(__file__), '../scripts'))) 0, os.path.abspath(os.path.join(os.path.dirname(__file__), "../scripts"))
)

View File

@@ -1,18 +1,16 @@
import unittest
import random import random
import string import string
import sys import sys
import unittest
from pathlib import Path 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 autogpt.config import Config
from config import Config from autogpt.memory.local import LocalCache
from memory.local import LocalCache
class TestLocalCache(unittest.TestCase): class TestLocalCache(unittest.TestCase):
def random_string(self, length): def random_string(self, length):
return ''.join(random.choice(string.ascii_letters) for _ in range(length)) return "".join(random.choice(string.ascii_letters) for _ in range(length))
def setUp(self): def setUp(self):
cfg = cfg = Config() cfg = cfg = Config()
@@ -21,10 +19,10 @@ class TestLocalCache(unittest.TestCase):
# Add example texts to the cache # Add example texts to the cache
self.example_texts = [ self.example_texts = [
'The quick brown fox jumps over the lazy dog', "The quick brown fox jumps over the lazy dog",
'I love machine learning and natural language processing', "I love machine learning and natural language processing",
'The cake is a lie, but the pie is always true', "The cake is a lie, but the pie is always true",
'ChatGPT is an advanced AI model for conversation' "ChatGPT is an advanced AI model for conversation",
] ]
for text in self.example_texts: for text in self.example_texts:
@@ -47,5 +45,5 @@ class TestLocalCache(unittest.TestCase):
self.assertIn(self.example_texts[1], relevant_texts) self.assertIn(self.example_texts[1], relevant_texts)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -7,10 +7,9 @@ from weaviate import Client
from weaviate.util import get_valid_uuid from weaviate.util import get_valid_uuid
from uuid import uuid4 from uuid import uuid4
sys.path.append(os.path.abspath('./scripts')) from autogpt.config import Config
from config import Config from autogpt.memory.weaviate import WeaviateMemory
from memory.weaviate import WeaviateMemory from autogpt.memory.base import get_ada_embedding
from memory.base import get_ada_embedding
@mock.patch.dict(os.environ, { @mock.patch.dict(os.environ, {
"WEAVIATE_HOST": "127.0.0.1", "WEAVIATE_HOST": "127.0.0.1",

View File

@@ -1,21 +1,23 @@
import os import os
import sys import sys
# Probably a better way:
sys.path.append(os.path.abspath('../scripts')) from autogpt.memory.local import LocalCache
from memory.local import LocalCache
def MockConfig(): def MockConfig():
return type('MockConfig', (object,), { return type(
'debug_mode': False, "MockConfig",
'continuous_mode': False, (object,),
'speak_mode': False, {
'memory_index': 'auto-gpt', "debug_mode": False,
}) "continuous_mode": False,
"speak_mode": False,
"memory_index": "auto-gpt",
},
)
class TestLocalCache(unittest.TestCase): class TestLocalCache(unittest.TestCase):
def setUp(self): def setUp(self):
self.cfg = MockConfig() self.cfg = MockConfig()
self.cache = LocalCache(self.cfg) self.cache = LocalCache(self.cfg)
@@ -50,5 +52,5 @@ class TestLocalCache(unittest.TestCase):
self.assertEqual(stats, (1, self.cache.data.embeddings.shape)) self.assertEqual(stats, (1, self.cache.data.embeddings.shape))
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -0,0 +1,99 @@
# Import the required libraries for unit testing
import os
import sys
import unittest
from autogpt.promptgenerator import PromptGenerator
# Create a test class for the PromptGenerator, subclassed from unittest.TestCase
class promptgenerator_tests(unittest.TestCase):
# Set up the initial state for each test method by creating an instance of PromptGenerator
def setUp(self):
self.generator = PromptGenerator()
# Test whether the add_constraint() method adds a constraint to the generator's constraints list
def test_add_constraint(self):
constraint = "Constraint1"
self.generator.add_constraint(constraint)
self.assertIn(constraint, self.generator.constraints)
# Test whether the add_command() method adds a command to the generator's commands list
def test_add_command(self):
command_label = "Command Label"
command_name = "command_name"
args = {"arg1": "value1", "arg2": "value2"}
self.generator.add_command(command_label, command_name, args)
command = {
"label": command_label,
"name": command_name,
"args": args,
}
self.assertIn(command, self.generator.commands)
# Test whether the add_resource() method adds a resource to the generator's resources list
def test_add_resource(self):
resource = "Resource1"
self.generator.add_resource(resource)
self.assertIn(resource, self.generator.resources)
# Test whether the add_performance_evaluation() method adds an evaluation to the generator's performance_evaluation list
def test_add_performance_evaluation(self):
evaluation = "Evaluation1"
self.generator.add_performance_evaluation(evaluation)
self.assertIn(evaluation, self.generator.performance_evaluation)
# Test whether the generate_prompt_string() method generates a prompt string with all the added constraints, commands, resources and evaluations
def test_generate_prompt_string(self):
constraints = ["Constraint1", "Constraint2"]
commands = [
{
"label": "Command1",
"name": "command_name1",
"args": {"arg1": "value1"},
},
{
"label": "Command2",
"name": "command_name2",
"args": {},
},
]
resources = ["Resource1", "Resource2"]
evaluations = ["Evaluation1", "Evaluation2"]
# Add all the constraints, commands, resources, and evaluations to the generator
for constraint in constraints:
self.generator.add_constraint(constraint)
for command in commands:
self.generator.add_command(
command["label"], command["name"], command["args"]
)
for resource in resources:
self.generator.add_resource(resource)
for evaluation in evaluations:
self.generator.add_performance_evaluation(evaluation)
# Generate the prompt string and verify its correctness
prompt_string = self.generator.generate_prompt_string()
self.assertIsNotNone(prompt_string)
for constraint in constraints:
self.assertIn(constraint, prompt_string)
for command in commands:
self.assertIn(command["name"], prompt_string)
# Check for each key-value pair in the command args dictionary
for key, value in command["args"].items():
self.assertIn(f'"{key}": "{value}"', prompt_string)
for resource in resources:
self.assertIn(resource, prompt_string)
for evaluation in evaluations:
self.assertIn(evaluation, prompt_string)
self.assertIn("constraints", prompt_string.lower())
self.assertIn("commands", prompt_string.lower())
self.assertIn("resources", prompt_string.lower())
self.assertIn("performance evaluation", prompt_string.lower())
# Run the tests when this script is executed
if __name__ == "__main__":
unittest.main()

View File

@@ -1,9 +1,9 @@
import unittest import unittest
from scripts.config import Config
from autogpt.config import Config
class TestConfig(unittest.TestCase): class TestConfig(unittest.TestCase):
def test_singleton(self): def test_singleton(self):
config1 = Config() config1 = Config()
config2 = Config() config2 = Config()
@@ -55,5 +55,5 @@ class TestConfig(unittest.TestCase):
self.assertTrue(config.debug_mode) self.assertTrue(config.debug_mode)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -1,7 +1,7 @@
import unittest import unittest
import tests.context
from scripts.json_parser import fix_and_parse_json import tests.context
from autogpt.json_parser import fix_and_parse_json
class TestParseJson(unittest.TestCase): class TestParseJson(unittest.TestCase):
@@ -21,7 +21,7 @@ class TestParseJson(unittest.TestCase):
# Test that an invalid JSON string raises an error when try_to_fix_with_gpt is False # 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' json_str = 'BEGIN: "name": "John" - "age": 30 - "city": "New York" :END'
with self.assertRaises(Exception): with self.assertRaises(Exception):
fix_and_parse_json(json_str, try_to_fix_with_gpt=False) fix_and_parse_json(json_str, try_to_fix_with_gpt=False)
def test_invalid_json_major_without_gpt(self): 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 # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
@@ -51,23 +51,22 @@ class TestParseJson(unittest.TestCase):
} }
}""" }"""
good_obj = { good_obj = {
"command": { "command": {
"name": "browse_website", "name": "browse_website",
"args": { "args": {"url": "https://github.com/Torantulino/Auto-GPT"},
"url": "https://github.com/Torantulino/Auto-GPT" },
} "thoughts": {
}, "text": "I suggest we start browsing the repository to find any issues that we can fix.",
"thoughts": "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",
"text": "I suggest we start browsing the repository to find any issues that we can fix.", "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
"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.", "speak": "I will start browsing the repository to find any issues we can fix.",
"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: # Assert that this raises an exception:
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) 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): 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 # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
@@ -90,24 +89,23 @@ class TestParseJson(unittest.TestCase):
} }
}""" }"""
good_obj = { good_obj = {
"command": { "command": {
"name": "browse_website", "name": "browse_website",
"args": { "args": {"url": "https://github.com/Torantulino/Auto-GPT"},
"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.",
},
} }
},
"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: # Assert that this raises an exception:
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) self.assertEqual(
fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj
)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -1,9 +1,6 @@
import unittest import unittest
import os
import sys from autogpt.json_parser import fix_and_parse_json
# Probably a better way:
sys.path.append(os.path.abspath('../scripts'))
from json_parser import fix_and_parse_json
class TestParseJson(unittest.TestCase): class TestParseJson(unittest.TestCase):
@@ -16,12 +13,18 @@ class TestParseJson(unittest.TestCase):
def test_invalid_json_minor(self): def test_invalid_json_minor(self):
# Test that an invalid JSON string can be fixed with gpt # Test that an invalid JSON string can be fixed with gpt
json_str = '{"name": "John", "age": 30, "city": "New York",}' 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"}) 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): 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 # 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' 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"}) 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): 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 # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
@@ -51,23 +54,22 @@ class TestParseJson(unittest.TestCase):
} }
}""" }"""
good_obj = { good_obj = {
"command": { "command": {
"name": "browse_website", "name": "browse_website",
"args": { "args": {"url": "https://github.com/Torantulino/Auto-GPT"},
"url": "https://github.com/Torantulino/Auto-GPT" },
} "thoughts": {
}, "text": "I suggest we start browsing the repository to find any issues that we can fix.",
"thoughts": "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",
"text": "I suggest we start browsing the repository to find any issues that we can fix.", "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
"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.", "speak": "I will start browsing the repository to find any issues we can fix.",
"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: # Assert that this raises an exception:
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) 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): 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 # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
@@ -90,24 +92,23 @@ class TestParseJson(unittest.TestCase):
} }
}""" }"""
good_obj = { good_obj = {
"command": { "command": {
"name": "browse_website", "name": "browse_website",
"args": { "args": {"url": "https://github.com/Torantulino/Auto-GPT"},
"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.",
},
} }
},
"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: # Assert that this raises an exception:
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj) self.assertEqual(
fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj
)
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View File

@@ -1,4 +1,3 @@
# Generated by CodiumAI # Generated by CodiumAI
# Dependencies: # Dependencies:
@@ -39,7 +38,6 @@ requests and parse HTML content, respectively.
class TestScrapeLinks: class TestScrapeLinks:
# Tests that the function returns a list of formatted hyperlinks when # Tests that the function returns a list of formatted hyperlinks when
# provided with a valid url that returns a webpage with hyperlinks. # provided with a valid url that returns a webpage with hyperlinks.
def test_valid_url_with_hyperlinks(self): def test_valid_url_with_hyperlinks(self):
@@ -54,8 +52,10 @@ class TestScrapeLinks:
# Mock the requests.get() function to return a response with sample HTML containing hyperlinks # Mock the requests.get() function to return a response with sample HTML containing hyperlinks
mock_response = mocker.Mock() mock_response = mocker.Mock()
mock_response.status_code = 200 mock_response.status_code = 200
mock_response.text = "<html><body><a href='https://www.google.com'>Google</a></body></html>" mock_response.text = (
mocker.patch('requests.get', return_value=mock_response) "<html><body><a href='https://www.google.com'>Google</a></body></html>"
)
mocker.patch("requests.get", return_value=mock_response)
# Call the function with a valid URL # Call the function with a valid URL
result = scrape_links("https://www.example.com") 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 the requests.get() function to return an HTTP error response
mock_response = mocker.Mock() mock_response = mocker.Mock()
mock_response.status_code = 404 mock_response.status_code = 404
mocker.patch('requests.get', return_value=mock_response) mocker.patch("requests.get", return_value=mock_response)
# Call the function with an invalid URL # Call the function with an invalid URL
result = scrape_links("https://www.invalidurl.com") result = scrape_links("https://www.invalidurl.com")
@@ -82,7 +82,7 @@ class TestScrapeLinks:
mock_response = mocker.Mock() mock_response = mocker.Mock()
mock_response.status_code = 200 mock_response.status_code = 200
mock_response.text = "<html><body><p>No hyperlinks here</p></body></html>" mock_response.text = "<html><body><p>No hyperlinks here</p></body></html>"
mocker.patch('requests.get', return_value=mock_response) mocker.patch("requests.get", return_value=mock_response)
# Call the function with a URL containing no hyperlinks # Call the function with a URL containing no hyperlinks
result = scrape_links("https://www.example.com") result = scrape_links("https://www.example.com")
@@ -105,7 +105,7 @@ class TestScrapeLinks:
</body> </body>
</html> </html>
""" """
mocker.patch('requests.get', return_value=mock_response) mocker.patch("requests.get", return_value=mock_response)
# Call the function being tested # Call the function being tested
result = scrape_links("https://www.example.com") result = scrape_links("https://www.example.com")

View File

@@ -1,9 +1,8 @@
# Generated by CodiumAI # Generated by CodiumAI
import requests import requests
from scripts.browse import scrape_text from autogpt.browse import scrape_text
""" """
Code Analysis Code Analysis
@@ -35,7 +34,6 @@ Additional aspects:
class TestScrapeText: class TestScrapeText:
# Tests that scrape_text() returns the expected text when given a valid URL. # Tests that scrape_text() returns the expected text when given a valid URL.
def test_scrape_text_with_valid_url(self, mocker): def test_scrape_text_with_valid_url(self, mocker):
# Mock the requests.get() method to return a response with expected text # Mock the requests.get() method to return a response with expected text
@@ -74,7 +72,7 @@ class TestScrapeText:
# Tests that the function returns an error message when the response status code is an http error (>=400). # Tests that the function returns an error message when the response status code is an http error (>=400).
def test_http_error(self, mocker): def test_http_error(self, mocker):
# Mock the requests.get() method to return a response with a 404 status code # 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.get", return_value=mocker.Mock(status_code=404))
# Call the function with a URL # Call the function with a URL
result = scrape_text("https://www.example.com") result = scrape_text("https://www.example.com")