Repository: Significant-Gravitas/AutoGPT
Pull request: Makes it possible to use gpt-3.5
(This is such a neat experiment. Thanks for open-sourcing!)

Fixes #12
Fixes #40 

Makes davinci-3.5-turbo the default model (faster and cheaper)
Allows for model configuration

I made JSON parsing a lot more forgiving, including using GPT to fix up the JSON if necessary.
I also improved the prompt to make it follow instructions a bit better.
I also refactored a bit. 
I also made it save my inputs to a yml file by default when it runs. (This could be a LOT better - very rough, but wanted to get this in)

P.S. Mind adding an MIT license to this?

Problem statement:
The model: `gpt-4` does not exist
Hi,
Amazing work you're doing here! ❤
I'm getting this message:

> 
> Traceback (most recent call last):
>   File "/Users/stas/Auto-GPT/AutonomousAI/main.py", line 154, in <module>
>     assistant_reply = chat.chat_with_ai(prompt, user_input, full_message_history, mem.permanent_memory, token_limit)
>                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/Users/stas/Auto-GPT/AutonomousAI/chat.py", line 48, in chat_with_ai
>     response = openai.ChatCompletion.create(
>                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_resources/chat_completion.py", line 25, in create
>     return super().create(*args, **kwargs)
>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_resources/abstract/engine_api_resource.py", line 153, in create
>     response, _, api_key = requestor.request(
>                            ^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 226, in request
>     resp, got_stream = self._interpret_response(result, stream)
>                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 619, in _interpret_response
>     self._interpret_response_line(
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 679, in _interpret_response_line
>     raise self.handle_error_response(
> **openai.error.InvalidRequestError: The model: `gpt-4` does not exist**

Does it mean I don't have an API access to gpt-4 yet? How can I use previous versions?


Error: Invalid JSON
I'm getting this argument in a loop

NEXT ACTION:  COMMAND = Error: ARGUMENTS = Invalid JSON
SYSTEM:  Command Error: returned: Unknown command Error:
Error: Invalid JSON

Hints from issue discussion:
You can apply for access to the GPT-4 API here:

https://openai.com/waitlist/gpt-4-api

Unfortunately, in my testing when I've tried to run Auto-GPT using GPT3.5 it does not function at all as the model does not understand it's task. 
I'm playing with v3.5 thanks to @Koobah fork: https://github.com/Koobah/Auto-GPT
It seems to be able to do some things: https://www.loom.com/share/9ddf4b806dfb4a948c7d728669ebac28

Got stopped by this error:

> > Traceback (most recent call last):
>   File "/Users/stas/dev/Auto-GPT/AutonomousAI/main.py", line 134, in <module>
>     assistant_reply = chat.chat_with_ai(prompt, user_input, full_message_history, mem.permanent_memory, token_limit)
>                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/Users/stas/dev/Auto-GPT/AutonomousAI/chat.py", line 50, in chat_with_ai
>     response = openai.ChatCompletion.create(
>                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_resources/chat_completion.py", line 25, in create
>     return super().create(*args, **kwargs)
>            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_resources/abstract/engine_api_resource.py", line 153, in create
>     response, _, api_key = requestor.request(
>                            ^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 226, in request
>     resp, got_stream = self._interpret_response(result, stream)
>                        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 619, in _interpret_response
>     self._interpret_response_line(
>   File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 679, in _interpret_response_line
>     raise self.handle_error_response(
> openai.error.InvalidRequestError: This model's maximum context length is 4097 tokens. However, your messages resulted in 4121 tokens. Please reduce the length of the messages.


It did it! Completed a task on v.3.5!
Here's a screencast if anybody is curious: https://www.loom.com/share/9bf888d9c925474899257d072f1a562f


Wow! 🤯
I didn't know that was possible, great work guys! @stan-voo @Koobah 

I'd tried getting 3.5 to work in the past and it refused to acknowledge the prompt. Great idea asking it to parse it's final output as JSON.

If you want to submit a pull request that would be a huge help!:

- [ ] - Add a model argument (If none is provided, default to gpt-4)
- [ ] - Create different prompt.txt files for each model (this appears to be necessary), load the appropriate one when building the prompt.
- [ ] - Add model to Config, set on start-up and get every time gpt-4 is currently mentioned.


GPT3.5 is so much cheaper, allowing for much more testing and development.

In future we could even get GPT4 instances of Auto-GPT to cheaply spin up entire sub-instances of Auto-GPT running GPT3.5 for multiple step tasks...
Guys, glad that it helped you. Just be very cautious when using my code. This is my very first programming attempt. I have no clue what I am doing :)
Btw, the idea I am working on is to create specialist GPT instances project managers, marketers, operators), where the bot would have it's own complex prompt replicating what a person in such role would do
Brilliant! Make sure you're up-to-date, as I'm adding features all the time.

 Please share your journey with us over at [Discussions](https://github.com/Torantulino/Auto-GPT/discussions/14), I'd love to see how things progress as you go.
https://github.com/Torantulino/Auto-GPT/pull/19
> I'm playing with v3.5 thanks to @Koobah fork: https://github.com/Koobah/Auto-GPT It seems to be able to do some things: https://www.loom.com/share/9ddf4b806dfb4a948c7d728669ebac28
> 
> Got stopped by this error:
> 
> > > Traceback (most recent call last):
> > > File "/Users/stas/dev/Auto-GPT/AutonomousAI/main.py", line 134, in 
> > > assistant_reply = chat.chat_with_ai(prompt, user_input, full_message_history, mem.permanent_memory, token_limit)
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/Users/stas/dev/Auto-GPT/AutonomousAI/chat.py", line 50, in chat_with_ai
> > > response = openai.ChatCompletion.create(
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/usr/local/lib/python3.11/site-packages/openai/api_resources/chat_completion.py", line 25, in create
> > > return super().create(*args, **kwargs)
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/usr/local/lib/python3.11/site-packages/openai/api_resources/abstract/engine_api_resource.py", line 153, in create
> > > response, _, api_key = requestor.request(
> > > ^^^^^^^^^^^^^^^^^^
> > > File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 226, in request
> > > resp, got_stream = self._interpret_response(result, stream)
> > > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> > > File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 619, in _interpret_response
> > > self._interpret_response_line(
> > > File "/usr/local/lib/python3.11/site-packages/openai/api_requestor.py", line 679, in _interpret_response_line
> > > raise self.handle_error_response(
> > > openai.error.InvalidRequestError: This model's maximum context length is 4097 tokens. However, your messages resulted in 4121 tokens. Please reduce the length of the messages.

I dont understand it, I download Koobah's fork, how to run it with gpt 3.5 key ?  I used command git clone https://github.com/Koobah/Auto-GPT , but I dont find main.py in his repository

Update: I am roll it out. Just need to create keys.py (read readme) in autonomousAI path and run main.py !
Note that another "autonomous agent" experiment by Yohei (which is ~~more~~ quite popular on Twitter, but not open source) has produced impressive demos [using GPT3](https://twitter.com/yoheinakajima/status/1641940249847934976). Yohei has recently [publicized](https://twitter.com/yoheinakajima/status/1640934493489070080) the architecture of the system, and I think there are things to learn from it, e.g. using a task queue, and using a vector store for long-term memory rather than files. But that system doesn't implement code execution yet, let alone code improvement.
Looks like a bug in what is getting sent to the AI:
```
Goal 3:
------------ CONTEXT SENT TO AI ---------------
System: Permanent memory: []
User: NEXT COMMAND
----------- END OF CONTEXT ----------------
assistant reply: What would you like me to do?
Error: Invalid JSON
 What would you like me to do?
NEXT ACTION:  COMMAND = Error: ARGUMENTS = Invalid JSON
Enter 'y' to authorise command or 'n' to exit program...
Input:
```
Same with default run
some problems with escaping in string values. looks like due to prompts(initial or generated ones). we should add checks and escaping for strings
example, if you require a Google Search, please provide the following command: ``` { "command": { "name": "google", "args": { "input": "<search>" } } } ``` For executing a Python file, you can issue the command: ``` { "command": { "name": "execute_python_file", "args": { "file": "<file>" } } } ``` If you have any questions or require assistance with a particular command or task, please don't hesitate to ask.
NEXT ACTION:  COMMAND = Error: ARGUMENTS = Invalid JSON
SYSTEM:  Command Error: returned: Unknown command Error:
Solution here:
https://github.com/Torantulino/Auto-GPT/issues/21#issuecomment-1493458050

Commit message: Remove unused imports

Patch to review:
diff --git a/.env.template b/.env.template
index a598aa7b1a55..cbf0cd9bc9b4 100644
--- a/.env.template
+++ b/.env.template
@@ -1,2 +1,4 @@
 OPENAI_API_KEY=your-openai-api-key
 ELEVENLABS_API_KEY=your-elevenlabs-api-key
+SMART_LLM_MODEL="gpt-4"
+FAST_LLM_MODEL="gpt-3.5-turbo"
\ No newline at end of file
diff --git a/requirements-alternative.txt b/requirements-alternative.txt
new file mode 100644
index 000000000000..a2f2723d75be
--- /dev/null
+++ b/requirements-alternative.txt
@@ -0,0 +1,14 @@
+# I wasn't having any luck installing the requirements.txt file in Mac or Linux
+# But this seems to work.
+# The biggest difference is docker 5 instead of 6, because of this silliness:
+#
+# The conflict is caused by:
+#     The user requested requests>=2.26.0
+#     docker 6.0.1 depends on requests>=2.26.0
+#     googlesearch-python 1.1.0 depends on requests==2.25.1
+docker==5.0.3
+
+# I'd love to fix this in a cleaner way
+
+# Now go ahead and install the rest of what requirements.txt says:
+-r requirements.txt
diff --git a/requirements.txt b/requirements.txt
index 6ae3a1ea0a1e..60c2d3ea5e6c 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,9 +1,11 @@
 beautifulsoup4==4.9.3
 colorama==0.4.6
+dirtyjson==1.0.8
 docker==5.0.3
 googlesearch-python==1.1.0
 openai==0.27.2
 playsound==1.3.0
 python-dotenv==1.0.0
+pyyaml==6.0
 readability-lxml==0.8.1
 requests==2.25.1
diff --git a/scripts/agent_manager.py b/scripts/agent_manager.py
index 9939332b804b..ef0227dea2a5 100644
--- a/scripts/agent_manager.py
+++ b/scripts/agent_manager.py
@@ -1,4 +1,4 @@
-import openai
+from llm_utils import create_chat_completion
 
 next_key = 0
 agents = {}  # key, (task, full_message_history, model)
@@ -13,13 +13,11 @@ def create_agent(task, prompt, model):
     messages = [{"role": "user", "content": prompt}, ]
 
     # Start GTP3 instance
-    response = openai.ChatCompletion.create(
+    agent_reply = create_chat_completion(
         model=model,
         messages=messages,
     )
 
-    agent_reply = response.choices[0].message["content"]
-
     # Update full message history
     messages.append({"role": "assistant", "content": agent_reply})
 
@@ -42,14 +40,11 @@ def message_agent(key, message):
     messages.append({"role": "user", "content": message})
 
     # Start GTP3 instance
-    response = openai.ChatCompletion.create(
+    agent_reply = create_chat_completion(
         model=model,
         messages=messages,
     )
 
-    # Get agent response
-    agent_reply = response.choices[0].message["content"]
-
     # Update full message history
     messages.append({"role": "assistant", "content": agent_reply})
 
diff --git a/scripts/ai_config.py b/scripts/ai_config.py
new file mode 100644
index 000000000000..e35de06dfc16
--- /dev/null
+++ b/scripts/ai_config.py
@@ -0,0 +1,56 @@
+import yaml
+import data
+
+class AIConfig:
+    def __init__(self, ai_name="", ai_role="", ai_goals=[]):
+        self.ai_name = ai_name
+        self.ai_role = ai_role
+        self.ai_goals = ai_goals
+
+    # @classmethod
+    # def create_from_user_prompts(cls):
+    #     ai_name = input("Name your AI: ") or "Entrepreneur-GPT"
+    #     ai_role = input(f"{ai_name} is: ") or "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth."
+    #     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 = 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"]
+    #     return cls(ai_name, ai_role, ai_goals)
+
+    @classmethod
+    def load(cls, config_file="config.yaml"):
+        # Load variables from yaml file if it exists
+        try:
+            with open(config_file) as file:
+                config_params = yaml.load(file, Loader=yaml.FullLoader)
+        except FileNotFoundError:
+            config_params = {}
+
+        ai_name = config_params.get("ai_name", "")
+        ai_role = config_params.get("ai_role", "")
+        ai_goals = config_params.get("ai_goals", [])
+
+        return cls(ai_name, ai_role, ai_goals)
+
+    def save(self, config_file="config.yaml"):
+        config = {"ai_name": self.ai_name, "ai_role": self.ai_role, "ai_goals": self.ai_goals}
+        with open(config_file, "w") as file:
+            documents = yaml.dump(config, file)
+
+    def construct_full_prompt(self):
+        prompt_start = """Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications."""
+
+        # Construct full prompt
+        full_prompt = f"You are {self.ai_name}, {self.ai_role}\n{prompt_start}\n\nGOALS:\n\n"
+        for i, goal in enumerate(self.ai_goals):
+            full_prompt += f"{i+1}. {goal}\n"
+
+        full_prompt += f"\n\n{data.load_prompt()}"
+        return full_prompt
diff --git a/scripts/ai_functions.py b/scripts/ai_functions.py
index b79c4dbce18b..0d27132dfe41 100644
--- a/scripts/ai_functions.py
+++ b/scripts/ai_functions.py
@@ -1,43 +1,23 @@
 from typing import List, Optional
 import json
-import openai
-
-
-# This is a magic function that can do anything with no-code. See
-# https://github.com/Torantulino/AI-Functions for more info.
-def call_ai_function(function, args, description, model="gpt-4"):
-    # parse args to comma seperated string
-    args = ", ".join(args)
-    messages = [
-        {
-            "role": "system",
-            "content": f"You are now the following python function: ```# {description}\n{function}```\n\nOnly respond with your `return` value.",
-        },
-        {"role": "user", "content": args},
-    ]
-
-    response = openai.ChatCompletion.create(
-        model=model, messages=messages, temperature=0
-    )
-
-    return response.choices[0].message["content"]
-
+from config import Config
+from call_ai_function import call_ai_function
+from json_parser import fix_and_parse_json
+cfg = Config()
 
 # Evaluating code
 
-
 def evaluate_code(code: str) -> List[str]:
     function_string = "def analyze_code(code: str) -> List[str]:"
     args = [code]
     description_string = """Analyzes the given code and returns a list of suggestions for improvements."""
 
     result_string = call_ai_function(function_string, args, description_string)
-    return json.loads(result_string)
+    return fix_and_parse_json.loads(result_string)
 
 
 # Improving code
 
-
 def improve_code(suggestions: List[str], code: str) -> str:
     function_string = (
         "def generate_improved_code(suggestions: List[str], code: str) -> str:"
@@ -61,3 +41,5 @@ def write_tests(code: str, focus: List[str]) -> str:
 
     result_string = call_ai_function(function_string, args, description_string)
     return result_string
+
+
diff --git a/scripts/browse.py b/scripts/browse.py
index 33911f2065c7..f096c5f3e23c 100644
--- a/scripts/browse.py
+++ b/scripts/browse.py
@@ -1,9 +1,9 @@
-from googlesearch import search
 import requests
 from bs4 import BeautifulSoup
-from readability import Document
-import openai
+from config import Config
+from llm_utils import create_chat_completion
 
+cfg = Config()
 
 def scrape_text(url):
     response = requests.get(url)
@@ -99,13 +99,11 @@ def summarize_text(text, is_website=True):
                     chunk},
             ]
 
-        response = openai.ChatCompletion.create(
-            model="gpt-3.5-turbo",
+        summary = create_chat_completion(
+            model=cfg.fast_llm_model,
             messages=messages,
             max_tokens=300,
         )
-
-        summary = response.choices[0].message.content
         summaries.append(summary)
     print("Summarized " + str(len(chunks)) + " chunks.")
 
@@ -127,11 +125,10 @@ def summarize_text(text, is_website=True):
                 combined_summary},
         ]
 
-    response = openai.ChatCompletion.create(
-        model="gpt-3.5-turbo",
+    final_summary = create_chat_completion(
+        model=cfg.fast_llm_model,
         messages=messages,
         max_tokens=300,
     )
 
-    final_summary = response.choices[0].message.content
     return final_summary
diff --git a/scripts/call_ai_function.py b/scripts/call_ai_function.py
new file mode 100644
index 000000000000..0c864b490909
--- /dev/null
+++ b/scripts/call_ai_function.py
@@ -0,0 +1,25 @@
+from config import Config
+cfg = Config()
+
+from llm_utils import create_chat_completion
+
+# This is a magic function that can do anything with no-code. See
+# https://github.com/Torantulino/AI-Functions for more info.
+def call_ai_function(function, args, description, model=cfg.smart_llm_model):
+    # For each arg, if any are None, convert to "None":
+    args = [str(arg) if arg is not None else "None" for arg in args]
+    # parse args to comma seperated string
+    args = ", ".join(args)
+    messages = [
+        {
+            "role": "system",
+            "content": f"You are now the following python function: ```# {description}\n{function}```\n\nOnly respond with your `return` value.",
+        },
+        {"role": "user", "content": args},
+    ]
+
+    response = create_chat_completion(
+        model=model, messages=messages, temperature=0
+    )
+
+    return response
diff --git a/scripts/chat.py b/scripts/chat.py
index e6aa278150cc..36568f58fe4e 100644
--- a/scripts/chat.py
+++ b/scripts/chat.py
@@ -1,14 +1,10 @@
-import os
 import time
 import openai
 from dotenv import load_dotenv
+from config import Config
+cfg = Config()
 
-# Load environment variables from .env file
-load_dotenv()
-
-# Initialize the OpenAI API client
-openai.api_key = os.getenv("OPENAI_API_KEY")
-
+from llm_utils import create_chat_completion
 
 def create_chat_message(role, content):
     """
@@ -30,7 +26,7 @@ def chat_with_ai(
         full_message_history,
         permanent_memory,
         token_limit,
-        debug=False):
+        debug=True):
     while True:
         try:
             """
@@ -65,13 +61,12 @@ def chat_with_ai(
                         f"{message['role'].capitalize()}: {message['content']}")
                 print("----------- END OF CONTEXT ----------------")
 
-            response = openai.ChatCompletion.create(
-                model="gpt-4",
+            # TODO: use a model defined elsewhere, so that model can contain temperature and other settings we care about
+            assistant_reply = create_chat_completion(
+                model=cfg.smart_llm_model,
                 messages=current_context,
             )
 
-            assistant_reply = response.choices[0].message["content"]
-
             # Update full message history
             full_message_history.append(
                 create_chat_message(
@@ -82,5 +77,6 @@ def chat_with_ai(
 
             return assistant_reply
         except openai.error.RateLimitError:
+            # TODO: WHen we switch to langchain, this is built in
             print("Error: ", "API Rate Limit Reached. Waiting 10 seconds...")
             time.sleep(10)
diff --git a/scripts/commands.py b/scripts/commands.py
index 610856321b9d..ff65f1a16916 100644
--- a/scripts/commands.py
+++ b/scripts/commands.py
@@ -8,12 +8,13 @@
 import ai_functions as ai
 from file_operations import read_file, write_to_file, append_to_file, delete_file
 from execute_code import execute_python_file
+from json_parser import fix_and_parse_json
 cfg = Config()
 
 
 def get_command(response):
     try:
-        response_json = json.loads(response)
+        response_json = fix_and_parse_json(response)
         command = response_json["command"]
         command_name = command["name"]
         arguments = command["args"]
@@ -163,7 +164,7 @@ def shutdown():
     quit()
 
 
-def start_agent(name, task, prompt, model="gpt-3.5-turbo"):
+def start_agent(name, task, prompt, model=cfg.fast_llm_model):
     global cfg
 
     # Remove underscores from name
diff --git a/scripts/config.py b/scripts/config.py
index bc7ebf71804f..4c892b8f28f0 100644
--- a/scripts/config.py
+++ b/scripts/config.py
@@ -1,3 +1,9 @@
+import os
+
+from dotenv import load_dotenv
+# Load environment variables from .env file
+load_dotenv()
+
 class Singleton(type):
     """
     Singleton metaclass for ensuring only one instance of a class.
@@ -21,9 +27,46 @@ class Config(metaclass=Singleton):
     def __init__(self):
         self.continuous_mode = False
         self.speak_mode = False
+        # TODO - make these models be self-contained, using langchain, so we can configure them once and call it good 
+        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.thinking_token_limit = 4000
+        # Initialize the OpenAI API client
+        self.openai_api_key = os.getenv("OPENAI_API_KEY")
+        self.elevenlabs_api_key = os.getenv("ELEVENLABS_API_KEY")
+        # Print values:
+        print("Config values:")
+        print(f"continuous_mode: {self.continuous_mode}")
+        print(f"speak_mode: {self.speak_mode}")
+        print(f"fast_llm_model: {self.fast_llm_model}")
+        print(f"smart_llm_model: {self.smart_llm_model}")
+        print(f"thinking_token_limit: {self.thinking_token_limit}")
+        print(f"openai_api_key: {self.openai_api_key}")
+        print(f"elevenlabs_api_key: {self.elevenlabs_api_key}")
+
+
+
 
     def set_continuous_mode(self, value: bool):
         self.continuous_mode = value
 
     def set_speak_mode(self, value: bool):
         self.speak_mode = value
+
+    def set_fast_llm_model(self, value: str):
+        self.fast_llm_model = value
+
+    def set_smart_llm_model(self, value: str):
+        self.smart_llm_model = value
+
+    def set_thinking_token_limit(self, value: int):
+        self.thinking_token_limit = value
+
+    def set_openai_api_key(self, value: str):
+        self.apiopenai_api_key_key = value
+    
+    def set_elevenlabs_api_key(self, value: str):
+        self.elevenlabs_api_key = value
+
+
+        
diff --git a/scripts/data.py b/scripts/data.py
index 194735578ed0..0a72cbbfc8af 100644
--- a/scripts/data.py
+++ b/scripts/data.py
@@ -1,7 +1,15 @@
+import os
+from pathlib import Path
+
+
 def load_prompt():
     try:
+        # get directory of this file:
+        file_dir = Path(os.path.dirname(os.path.realpath(__file__)))
+        data_dir = file_dir / "data"
+        prompt_file = data_dir / "prompt.txt"
         # Load the promt from data/prompt.txt
-        with open("data/prompt.txt", "r") as prompt_file:
+        with open(prompt_file, "r") as prompt_file:
             prompt = prompt_file.read()
 
         return prompt
diff --git a/scripts/data/prompt.txt b/scripts/data/prompt.txt
index 3cdc03001c94..60e02ca304e2 100644
--- a/scripts/data/prompt.txt
+++ b/scripts/data/prompt.txt
@@ -1,6 +1,6 @@
 CONSTRAINTS:
 
-1. 6000-word count limit for memory
+1. 4000-word count limit for memory
 2. No user assistance
 
 COMMANDS:
@@ -38,22 +38,24 @@ PERFORMANCE EVALUATION:
 3. Reflect on past decisions and strategies to refine your approach.
 4. Every command has a cost, so be smart and efficent. Aim to complete tasks in the least number of steps.
 
+You should only respond in JSON format as described below
+
 RESPONSE FORMAT:
 {
-"command":
-{
-"name": "command name",
-"args":
-{
-"arg name": "value"
+    "command": {
+        "name": "command name",
+        "args":{
+            "arg name": "value"
+        }
+    },
+    "thoughts":
+    {
+        "text": "thought",
+        "reasoning": "reasoning",
+        "plan": "- short bulleted\n- list that conveys\n- long-term plan",
+        "criticism": "constructive self-criticism"
+        "speak": "thoughts summary to say to user"
+    }
 }
-},
-"thoughts":
-{
-"text": "thought",
-"reasoning": "reasoning",
-"plan": "short bulleted long-term plan",
-"criticism": "constructive self-criticism"
-"speak": "thoughts summary to say to user"
-}
-}
\ No newline at end of file
+
+Ensure the response can be parsed by Python json.loads
\ No newline at end of file
diff --git a/scripts/json_parser.py b/scripts/json_parser.py
new file mode 100644
index 000000000000..16fb8fc76816
--- /dev/null
+++ b/scripts/json_parser.py
@@ -0,0 +1,53 @@
+import dirtyjson
+from call_ai_function import call_ai_function
+from config import Config
+cfg = Config()
+
+def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True):
+    try:
+        return dirtyjson.loads(json_str)
+    except Exception as e:
+        # Let's do something manually - sometimes GPT responds with something BEFORE the braces:
+        # "I'm sorry, I don't understand. Please try again."{"text": "I'm sorry, I don't understand. Please try again.", "confidence": 0.0}
+        # So let's try to find the first brace and then parse the rest of the string
+        try:
+          brace_index = json_str.index("{")
+          json_str = json_str[brace_index:]
+          last_brace_index = json_str.rindex("}")
+          json_str = json_str[:last_brace_index+1]
+          return dirtyjson.loads(json_str)
+        except Exception as e:
+          if try_to_fix_with_gpt:
+            # Now try to fix this up using the ai_functions
+            return fix_json(json_str, None, True)
+          else:
+            raise e
+        
+# TODO: Make debug a global config var
+def fix_json(json_str: str, schema:str = None, debug=True) -> str:
+    # Try to fix the JSON using gpt:
+    function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
+    args = [json_str, schema]
+    description_string = """Fixes the provided JSON string to make it parseable. If the schema is provided, the JSON will be made to look like the schema, otherwise it will be made to look like a valid JSON object."""
+
+    # If it doesn't already start with a "`", add one:
+    if not json_str.startswith("`"):
+      json_str = "```json\n" + json_str + "\n```"
+    result_string = call_ai_function(
+        function_string, args, description_string, model=cfg.fast_llm_model
+    )
+    if debug:
+        print("------------ JSON FIX ATTEMPT ---------------")
+        print(f"Original JSON: {json_str}")
+        print(f"Fixed JSON: {result_string}")
+        print("----------- END OF FIX ATTEMPT ----------------")
+    try:
+        return dirtyjson.loads(result_string)
+    except:
+        # Log the exception:
+        print("Failed to fix JSON")
+        # Get the call stack:
+        import traceback
+        call_stack = traceback.format_exc()
+        print(call_stack)
+        return {}
diff --git a/scripts/llm_utils.py b/scripts/llm_utils.py
new file mode 100644
index 000000000000..41f396250f26
--- /dev/null
+++ b/scripts/llm_utils.py
@@ -0,0 +1,16 @@
+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=None, max_tokens=None)->str:
+    response = openai.ChatCompletion.create(
+        model=model,
+        messages=messages,
+        temperature=temperature,
+        max_tokens=max_tokens
+    )
+
+    return response.choices[0].message["content"]
diff --git a/scripts/main.py b/scripts/main.py
index e80bf5d08a14..40f8f4c49720 100644
--- a/scripts/main.py
+++ b/scripts/main.py
@@ -11,18 +11,15 @@
 from enum import Enum, auto
 import sys
 from config import Config
-from dotenv import load_dotenv
-
-
-# Load environment variables from .env file
-load_dotenv()
-
+from json_parser import fix_and_parse_json
+from ai_config import AIConfig
+import traceback
+import yaml
 
 class Argument(Enum):
     CONTINUOUS_MODE = "continuous-mode"
     SPEAK_MODE = "speak-mode"
 
-
 def print_to_console(
         title,
         title_color,
@@ -35,6 +32,8 @@ def print_to_console(
         speak.say_text(f"{title}. {content}")
     print(title_color + title + " " + Style.RESET_ALL, end="")
     if content:
+        if isinstance(content, list):
+            content = " ".join(content)
         words = content.split()
         for i, word in enumerate(words):
             print(word, end="", flush=True)
@@ -53,7 +52,7 @@ def print_assistant_thoughts(assistant_reply):
     global cfg
     try:
         # Parse and print Assistant response
-        assistant_reply_json = json.loads(assistant_reply)
+        assistant_reply_json = fix_and_parse_json(assistant_reply)
 
         assistant_thoughts = assistant_reply_json.get("thoughts")
         if assistant_thoughts:
@@ -80,8 +79,13 @@ def print_assistant_thoughts(assistant_reply):
         if assistant_thoughts_plan:
             print_to_console("PLAN:", Fore.YELLOW, "")
             if assistant_thoughts_plan:
-
-                # Split the input_string using the newline character and dash
+                # 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 dash
+                
                 lines = assistant_thoughts_plan.split('\n')
 
                 # Iterate through the lines and print each one with a bullet
@@ -103,11 +107,89 @@ def print_assistant_thoughts(assistant_reply):
         print_to_console("Error: Invalid JSON\n", Fore.RED, assistant_reply)
     # All other errors, return "Error: + error message"
     except Exception as e:
-        print_to_console("Error: \n", Fore.RED, str(e))
+        call_stack = traceback.format_exc()
+        print_to_console("Error: \n", Fore.RED, call_stack)
+
+def load_variables(config_file="config.yaml"):
+    # Load variables from yaml file if it exists
+    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 = input("Name your AI: ")
+        if ai_name == "":
+            ai_name = "Entrepreneur-GPT"
+
+    if not ai_role:        
+        ai_role = input(f"{ai_name} is: ")
+        if ai_role == "":
+            ai_role = "an AI designed to autonomously develop and run businesses with the sole goal of increasing your net worth."
+
+    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 = 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 an 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():
+    config = AIConfig.load()
+    if config.ai_name:
+        print_to_console(
+            f"Welcome back, {config.ai_name}!",
+            Fore.GREEN,
+            "Let's continue our journey.",
+            speak_text=True)
+        should_continue = input(f"Continue with the last settings? (Settings: {config.ai_name}, {config.ai_role}, {config.ai_goals}) (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():
+    ai_name = ""
     # Construct the prompt
     print_to_console(
         "Welcome to Auto-GPT! ",
@@ -143,7 +225,7 @@ def construct_prompt():
     print_to_console(
         "Enter up to 5 goals for your AI: ",
         Fore.GREEN,
-        "For example: \nIncrease net worth \nGrow Twitter Account \nDevelop and manage multiple businesses autonomously'")
+        "For example: \nIncrease net worth, Grow Twitter Account, Develop and manage multiple businesses autonomously'")
     print("Enter nothing to load defaults, enter nothing when finished.", flush=True)
     ai_goals = []
     for i in range(5):
@@ -155,19 +237,8 @@ def construct_prompt():
         ai_goals = ["Increase net worth", "Grow Twitter Account",
                     "Develop and manage multiple businesses autonomously"]
 
-    prompt = data.load_prompt()
-    prompt_start = """Your decisions must always be made independently without seeking user assistance. Play to your strengths as an LLM and pursue simple strategies with no legal complications."""
-
-    # Construct full prompt
-    full_prompt = f"You are {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
-
-# Check if the python script was executed with arguments, get those arguments
-
+    config = AIConfig(ai_name, ai_role, ai_goals)
+    return config
 
 def parse_arguments():
     global cfg
@@ -186,16 +257,21 @@ def parse_arguments():
             cfg.set_speak_mode(True)
 
 
-cfg = Config()
 
+# TODO: Better argument parsing:
+# TODO: fill in llm values here
+
+cfg = Config()
 parse_arguments()
 ai_name = ""
 prompt = construct_prompt()
+print(prompt)
 # Initialize variables
 full_message_history = []
-token_limit = 6000  # The maximum number of tokens allowed in the API call
+token_limit = cfg.thinking_token_limit  # The maximum number of tokens allowed in the API call
 result = None
-user_input = "NEXT COMMAND"
+# Make a constant:
+user_input = "Determine which next command to use, and respond using the format specified above:"
 
 # Interaction Loop
 while True:
@@ -208,6 +284,7 @@ def parse_arguments():
             mem.permanent_memory,
             token_limit)
 
+    print("assistant reply: "+assistant_reply)
     # Print Assistant thoughts
     print_assistant_thoughts(assistant_reply)
 
@@ -232,7 +309,7 @@ def parse_arguments():
         while True:
             console_input = input(Fore.MAGENTA + "Input:" + Style.RESET_ALL)
             if console_input.lower() == "y":
-                user_input = "NEXT COMMAND"
+                user_input = "GENERATE NEXT COMMAND JSON"
                 break
             elif console_input.lower() == "n":
                 user_input = "EXIT"
@@ -240,7 +317,7 @@ def parse_arguments():
             else:
                 continue
 
-        if user_input != "NEXT COMMAND":
+        if user_input != "GENERATE NEXT COMMAND JSON":
             print("Exiting...", flush=True)
             break
 
@@ -255,7 +332,7 @@ def parse_arguments():
             Fore.CYAN,
             f"COMMAND = {Fore.CYAN}{command_name}{Style.RESET_ALL}  ARGUMENTS = {Fore.CYAN}{arguments}{Style.RESET_ALL}")
 
-    # Exectute command
+    # Execute command
     if command_name.lower() != "error":
         result = f"Command {command_name} returned: {cmd.execute_command(command_name, arguments)}"
     else:
diff --git a/scripts/speak.py b/scripts/speak.py
index a0f29fc58634..2fbcbed26f51 100644
--- a/scripts/speak.py
+++ b/scripts/speak.py
@@ -1,20 +1,17 @@
 import os
 from playsound import playsound
 import requests
-from dotenv import load_dotenv
-
-
-# Load environment variables from .env file
-load_dotenv()
+from config import Config
+cfg = Config()
 
+# TODO: Nicer names for these ids
 voices = ["ErXwobaYiN019PkySvjV", "EXAVITQu4vr4xnSDxMaL"]
 
 tts_headers = {
     "Content-Type": "application/json",
-    "xi-api-key": os.getenv("ELEVENLABS_API_KEY")
+    "xi-api-key": cfg.elevenlabs_api_key
 }
 
-
 def say_text(text, voice_index=0):
     tts_url = "https://api.elevenlabs.io/v1/text-to-speech/{voice_id}".format(
         voice_id=voices[voice_index])
diff --git a/tests/json_tests.py b/tests/json_tests.py
new file mode 100644
index 000000000000..459da610b872
--- /dev/null
+++ b/tests/json_tests.py
@@ -0,0 +1,115 @@
+import unittest
+import os
+import sys
+# Probably a better way:
+sys.path.append(os.path.abspath('../scripts'))
+from json_parser import fix_and_parse_json
+
+class TestParseJson(unittest.TestCase):
+    def test_valid_json(self):
+        # Test that a valid JSON string is parsed correctly
+        json_str = '{"name": "John", "age": 30, "city": "New York"}'
+        obj = fix_and_parse_json(json_str)
+        self.assertEqual(obj, {"name": "John", "age": 30, "city": "New York"})
+    
+    def test_invalid_json_minor(self):
+        # Test that an invalid JSON string can be fixed with gpt
+        json_str = '{"name": "John", "age": 30, "city": "New York",}'
+        self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), {"name": "John", "age": 30, "city": "New York"})
+    
+    def test_invalid_json_major_with_gpt(self):
+        # Test that an invalid JSON string raises an error when try_to_fix_with_gpt is False
+        json_str = 'BEGIN: "name": "John" - "age": 30 - "city": "New York" :END'
+        self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=True), {"name": "John", "age": 30, "city": "New York"})
+
+    def test_invalid_json_major_without_gpt(self):
+        # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
+        json_str = 'BEGIN: "name": "John" - "age": 30 - "city": "New York" :END'
+        # Assert that this raises an exception:
+        with self.assertRaises(Exception):
+            fix_and_parse_json(json_str, try_to_fix_with_gpt=False)
+
+    def test_invalid_json_leading_sentence_with_gpt(self):
+        # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
+        json_str = """I suggest we start by browsing the repository to find any issues that we can fix.
+
+{
+    "command": {
+        "name": "browse_website",
+        "args":{
+            "url": "https://github.com/Torantulino/Auto-GPT"
+        }
+    },
+    "thoughts":
+    {
+        "text": "I suggest we start browsing the repository to find any issues that we can fix.",
+        "reasoning": "Browsing the repository will give us an idea of the current state of the codebase and identify any issues that we can address to improve the repo.",
+        "plan": "- Look through the repository to find any issues.\n- Investigate any issues to determine what needs to be fixed\n- Identify possible solutions to fix the issues\n- Open Pull Requests with fixes",
+        "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
+        "speak": "I will start browsing the repository to find any issues we can fix."
+    }
+}"""
+        good_obj = {
+          "command": {
+              "name": "browse_website",
+              "args":{
+                  "url": "https://github.com/Torantulino/Auto-GPT"
+              }
+          },
+          "thoughts":
+          {
+              "text": "I suggest we start browsing the repository to find any issues that we can fix.",
+              "reasoning": "Browsing the repository will give us an idea of the current state of the codebase and identify any issues that we can address to improve the repo.",
+              "plan": "- Look through the repository to find any issues.\n- Investigate any issues to determine what needs to be fixed\n- Identify possible solutions to fix the issues\n- Open Pull Requests with fixes",
+              "criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
+              "speak": "I will start browsing the repository to find any issues we can fix."
+          }
+      }
+        # Assert that this raises an exception:
+        self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj)
+
+
+
+    def test_invalid_json_leading_sentence_with_gpt(self):
+        # Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
+        json_str = """I will first need to browse the repository (https://github.com/Torantulino/Auto-GPT) and identify any potential bugs that need fixing. I will use the "browse_website" command for this.
+
+{
+    "command": {
+        "name": "browse_website",
+        "args":{
+            "url": "https://github.com/Torantulino/Auto-GPT"
+        }
+    },
+    "thoughts":
+    {
+        "text": "Browsing the repository to identify potential bugs",
+        "reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.",
+        "plan": "- Analyze the repository for potential bugs and areas of improvement",
+        "criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.",
+        "speak": "I am browsing the repository to identify potential bugs."
+    }
+}"""
+        good_obj = {
+    "command": {
+        "name": "browse_website",
+        "args":{
+            "url": "https://github.com/Torantulino/Auto-GPT"
+        }
+    },
+    "thoughts":
+    {
+        "text": "Browsing the repository to identify potential bugs",
+        "reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.",
+        "plan": "- Analyze the repository for potential bugs and areas of improvement",
+        "criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.",
+        "speak": "I am browsing the repository to identify potential bugs."
+    }
+}
+        # Assert that this raises an exception:
+        self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj)
+
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file


Code review comments:
diff_hunk: @@ -1,3 +1,9 @@
+import os
+
path: scripts/config.py
text: @user1:
```suggestion
import openai
```