Skip to content

Commit 3e587bc

Browse files
committed
Make the json_parser more robust
For some reason the bot keeps prefacing its JSON. This fixes it for now.
1 parent a47da49 commit 3e587bc

File tree

5 files changed

+160
-58
lines changed

5 files changed

+160
-58
lines changed

scripts/ai_functions.py

+3-51
Original file line numberDiff line numberDiff line change
@@ -3,46 +3,23 @@
33
import openai
44
import dirtyjson
55
from config import Config
6-
6+
from call_ai_function import call_ai_function
7+
from json_parser import fix_and_parse_json
78
cfg = Config()
89

9-
# This is a magic function that can do anything with no-code. See
10-
# https://github.com/Torantulino/AI-Functions for more info.
11-
def call_ai_function(function, args, description, model=cfg.smart_llm_model):
12-
# For each arg, if any are None, convert to "None":
13-
args = [str(arg) if arg is not None else "None" for arg in args]
14-
# parse args to comma seperated string
15-
args = ", ".join(args)
16-
messages = [
17-
{
18-
"role": "system",
19-
"content": f"You are now the following python function: ```# {description}\n{function}```\n\nOnly respond with your `return` value.",
20-
},
21-
{"role": "user", "content": args},
22-
]
23-
24-
response = openai.ChatCompletion.create(
25-
model=model, messages=messages, temperature=0
26-
)
27-
28-
return response.choices[0].message["content"]
29-
30-
3110
# Evaluating code
3211

33-
3412
def evaluate_code(code: str) -> List[str]:
3513
function_string = "def analyze_code(code: str) -> List[str]:"
3614
args = [code]
3715
description_string = """Analyzes the given code and returns a list of suggestions for improvements."""
3816

3917
result_string = call_ai_function(function_string, args, description_string)
40-
return json.loads(result_string)
18+
return fix_and_parse_json.loads(result_string)
4119

4220

4321
# Improving code
4422

45-
4623
def improve_code(suggestions: List[str], code: str) -> str:
4724
function_string = (
4825
"def generate_improved_code(suggestions: List[str], code: str) -> str:"
@@ -68,28 +45,3 @@ def write_tests(code: str, focus: List[str]) -> str:
6845
return result_string
6946

7047

71-
# TODO: Make debug a global config var
72-
def fix_json(json_str: str, schema:str = None, debug=True) -> str:
73-
# Try to fix the JSON using gpt:
74-
function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
75-
args = [json_str, schema]
76-
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."""
77-
78-
result_string = call_ai_function(
79-
function_string, args, description_string, model=cfg.fast_llm_model
80-
)
81-
if debug:
82-
print("------------ JSON FIX ATTEMPT ---------------")
83-
print(f"Original JSON: {json_str}")
84-
print(f"Fixed JSON: {result_string}")
85-
print("----------- END OF FIX ATTEMPT ----------------")
86-
try:
87-
return dirtyjson.loads(result_string)
88-
except:
89-
# Log the exception:
90-
print("Failed to fix JSON")
91-
# Get the call stack:
92-
import traceback
93-
call_stack = traceback.format_exc()
94-
print(call_stack)
95-
return {}

scripts/call_ai_function.py

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from typing import List, Optional
2+
import json
3+
import openai
4+
import dirtyjson
5+
from config import Config
6+
cfg = Config()
7+
8+
# This is a magic function that can do anything with no-code. See
9+
# https://github.com/Torantulino/AI-Functions for more info.
10+
def call_ai_function(function, args, description, model=cfg.smart_llm_model):
11+
# For each arg, if any are None, convert to "None":
12+
args = [str(arg) if arg is not None else "None" for arg in args]
13+
# parse args to comma seperated string
14+
args = ", ".join(args)
15+
messages = [
16+
{
17+
"role": "system",
18+
"content": f"You are now the following python function: ```# {description}\n{function}```\n\nOnly respond with your `return` value.",
19+
},
20+
{"role": "user", "content": args},
21+
]
22+
23+
response = openai.ChatCompletion.create(
24+
model=model, messages=messages, temperature=0
25+
)
26+
27+
return response.choices[0].message["content"]

scripts/commands.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,13 @@
88
import ai_functions as ai
99
from file_operations import read_file, write_to_file, append_to_file, delete_file
1010
from execute_code import execute_python_file
11+
from json_parser import fix_and_parse_json
1112
cfg = Config()
1213

1314

1415
def get_command(response):
1516
try:
16-
response_json = json.loads(response)
17+
response_json = fix_and_parse_json(response)
1718
command = response_json["command"]
1819
command_name = command["name"]
1920
arguments = command["args"]

scripts/json_parser.py

+47-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,53 @@
11
import dirtyjson
2-
from ai_functions import fix_json
2+
from call_ai_function import call_ai_function
3+
from config import Config
4+
cfg = Config()
35

46
def fix_and_parse_json(json_str: str, try_to_fix_with_gpt: bool = True):
57
try:
68
return dirtyjson.loads(json_str)
79
except Exception as e:
8-
if try_to_fix_with_gpt:
9-
# Now try to fix this up using the ai_functions
10-
return fix_json(json_str, None, True)
11-
else:
12-
raise e
10+
# Let's do something manually - sometimes GPT responds with something BEFORE the braces:
11+
# "I'm sorry, I don't understand. Please try again."{"text": "I'm sorry, I don't understand. Please try again.", "confidence": 0.0}
12+
# So let's try to find the first brace and then parse the rest of the string
13+
try:
14+
brace_index = json_str.index("{")
15+
json_str = json_str[brace_index:]
16+
last_brace_index = json_str.rindex("}")
17+
json_str = json_str[:last_brace_index+1]
18+
return dirtyjson.loads(json_str)
19+
except Exception as e:
20+
if try_to_fix_with_gpt:
21+
# Now try to fix this up using the ai_functions
22+
return fix_json(json_str, None, True)
23+
else:
24+
raise e
25+
26+
# TODO: Make debug a global config var
27+
def fix_json(json_str: str, schema:str = None, debug=True) -> str:
28+
# Try to fix the JSON using gpt:
29+
function_string = "def fix_json(json_str: str, schema:str=None) -> str:"
30+
args = [json_str, schema]
31+
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."""
32+
33+
# If it doesn't already start with a "`", add one:
34+
if not json_str.startswith("`"):
35+
json_str = "```json\n" + json_str + "\n```"
36+
result_string = call_ai_function(
37+
function_string, args, description_string, model=cfg.fast_llm_model
38+
)
39+
if debug:
40+
print("------------ JSON FIX ATTEMPT ---------------")
41+
print(f"Original JSON: {json_str}")
42+
print(f"Fixed JSON: {result_string}")
43+
print("----------- END OF FIX ATTEMPT ----------------")
44+
try:
45+
return dirtyjson.loads(result_string)
46+
except:
47+
# Log the exception:
48+
print("Failed to fix JSON")
49+
# Get the call stack:
50+
import traceback
51+
call_stack = traceback.format_exc()
52+
print(call_stack)
53+
return {}

tests/json_tests.py

+81
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,87 @@ def test_invalid_json_major_without_gpt(self):
2929
with self.assertRaises(Exception):
3030
fix_and_parse_json(json_str, try_to_fix_with_gpt=False)
3131

32+
def test_invalid_json_leading_sentence_with_gpt(self):
33+
# Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
34+
json_str = """I suggest we start by browsing the repository to find any issues that we can fix.
35+
36+
{
37+
"command": {
38+
"name": "browse_website",
39+
"args":{
40+
"url": "https://github.com/Torantulino/Auto-GPT"
41+
}
42+
},
43+
"thoughts":
44+
{
45+
"text": "I suggest we start browsing the repository to find any issues that we can fix.",
46+
"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.",
47+
"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",
48+
"criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
49+
"speak": "I will start browsing the repository to find any issues we can fix."
50+
}
51+
}"""
52+
good_obj = {
53+
"command": {
54+
"name": "browse_website",
55+
"args":{
56+
"url": "https://github.com/Torantulino/Auto-GPT"
57+
}
58+
},
59+
"thoughts":
60+
{
61+
"text": "I suggest we start browsing the repository to find any issues that we can fix.",
62+
"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.",
63+
"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",
64+
"criticism": "I should be careful while browsing so as not to accidentally introduce any new bugs or issues.",
65+
"speak": "I will start browsing the repository to find any issues we can fix."
66+
}
67+
}
68+
# Assert that this raises an exception:
69+
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj)
70+
71+
72+
73+
def test_invalid_json_leading_sentence_with_gpt(self):
74+
# Test that a REALLY invalid JSON string raises an error when try_to_fix_with_gpt is False
75+
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.
76+
77+
{
78+
"command": {
79+
"name": "browse_website",
80+
"args":{
81+
"url": "https://github.com/Torantulino/Auto-GPT"
82+
}
83+
},
84+
"thoughts":
85+
{
86+
"text": "Browsing the repository to identify potential bugs",
87+
"reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.",
88+
"plan": "- Analyze the repository for potential bugs and areas of improvement",
89+
"criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.",
90+
"speak": "I am browsing the repository to identify potential bugs."
91+
}
92+
}"""
93+
good_obj = {
94+
"command": {
95+
"name": "browse_website",
96+
"args":{
97+
"url": "https://github.com/Torantulino/Auto-GPT"
98+
}
99+
},
100+
"thoughts":
101+
{
102+
"text": "Browsing the repository to identify potential bugs",
103+
"reasoning": "Before fixing bugs, I need to identify what needs fixing. I will use the 'browse_website' command to analyze the repository.",
104+
"plan": "- Analyze the repository for potential bugs and areas of improvement",
105+
"criticism": "I need to ensure I am thorough and pay attention to detail while browsing the repository.",
106+
"speak": "I am browsing the repository to identify potential bugs."
107+
}
108+
}
109+
# Assert that this raises an exception:
110+
self.assertEqual(fix_and_parse_json(json_str, try_to_fix_with_gpt=False), good_obj)
111+
112+
32113

33114
if __name__ == '__main__':
34115
unittest.main()

0 commit comments

Comments
 (0)