Spaces:
Runtime error
Runtime error
"""This module contains functions to fix JSON strings generated by LLM models, such as ChatGPT, using the assistance | |
of the ChatGPT API or LLM models.""" | |
from __future__ import annotations | |
import contextlib | |
import json | |
from typing import Any, Dict | |
from colorama import Fore | |
from regex import regex | |
from autogpt.config import Config | |
from autogpt.json_utils.json_fix_general import correct_json | |
from autogpt.llm_utils import call_ai_function | |
from autogpt.logs import logger | |
from autogpt.speech import say_text | |
JSON_SCHEMA = """ | |
{ | |
"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" | |
} | |
} | |
""" | |
CFG = Config() | |
def auto_fix_json(json_string: str, schema: str) -> str: | |
"""Fix the given JSON string to make it parseable and fully compliant with | |
the provided schema using GPT-3. | |
Args: | |
json_string (str): The JSON string to fix. | |
schema (str): The schema to use to fix the JSON. | |
Returns: | |
str: The fixed JSON string. | |
""" | |
# Try to fix the JSON using GPT: | |
function_string = "def fix_json(json_string: str, schema:str=None) -> str:" | |
args = [f"'''{json_string}'''", f"'''{schema}'''"] | |
description_string = ( | |
"This function takes a JSON string and ensures that it" | |
" is parseable and fully compliant with the provided schema. If an object" | |
" or field specified in the schema isn't contained within the correct JSON," | |
" it is omitted. The function also escapes any double quotes within JSON" | |
" string values to ensure that they are valid. If the JSON string contains" | |
" any None or NaN values, they are replaced with null before being parsed." | |
) | |
# If it doesn't already start with a "`", add one: | |
if not json_string.startswith("`"): | |
json_string = "```json\n" + json_string + "\n```" | |
result_string = call_ai_function( | |
function_string, args, description_string, model=CFG.fast_llm_model | |
) | |
logger.debug("------------ JSON FIX ATTEMPT ---------------") | |
logger.debug(f"Original JSON: {json_string}") | |
logger.debug("-----------") | |
logger.debug(f"Fixed JSON: {result_string}") | |
logger.debug("----------- END OF FIX ATTEMPT ----------------") | |
try: | |
json.loads(result_string) # just check the validity | |
return result_string | |
except json.JSONDecodeError: # noqa: E722 | |
# Get the call stack: | |
# import traceback | |
# call_stack = traceback.format_exc() | |
# print(f"Failed to fix JSON: '{json_string}' "+call_stack) | |
return "failed" | |
def fix_json_using_multiple_techniques(assistant_reply: str) -> Dict[Any, Any]: | |
"""Fix the given JSON string to make it parseable and fully compliant with two techniques. | |
Args: | |
json_string (str): The JSON string to fix. | |
Returns: | |
str: The fixed JSON string. | |
""" | |
# Parse and print Assistant response | |
assistant_reply_json = fix_and_parse_json(assistant_reply) | |
if assistant_reply_json == {}: | |
assistant_reply_json = attempt_to_fix_json_by_finding_outermost_brackets( | |
assistant_reply | |
) | |
if assistant_reply_json != {}: | |
return assistant_reply_json | |
logger.error( | |
"Error: The following AI output couldn't be converted to a JSON:\n", | |
assistant_reply, | |
) | |
if CFG.speak_mode: | |
say_text("I have received an invalid JSON response from the OpenAI API.") | |
return {} | |
def fix_and_parse_json( | |
json_to_load: str, try_to_fix_with_gpt: bool = True | |
) -> Dict[Any, Any]: | |
"""Fix and parse JSON string | |
Args: | |
json_to_load (str): The JSON string. | |
try_to_fix_with_gpt (bool, optional): Try to fix the JSON with GPT. | |
Defaults to True. | |
Returns: | |
str or dict[Any, Any]: The parsed JSON. | |
""" | |
with contextlib.suppress(json.JSONDecodeError): | |
json_to_load = json_to_load.replace("\t", "") | |
return json.loads(json_to_load) | |
with contextlib.suppress(json.JSONDecodeError): | |
json_to_load = correct_json(json_to_load) | |
return json.loads(json_to_load) | |
# 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_to_load.index("{") | |
maybe_fixed_json = json_to_load[brace_index:] | |
last_brace_index = maybe_fixed_json.rindex("}") | |
maybe_fixed_json = maybe_fixed_json[: last_brace_index + 1] | |
return json.loads(maybe_fixed_json) | |
except (json.JSONDecodeError, ValueError) as e: | |
return try_ai_fix(try_to_fix_with_gpt, e, json_to_load) | |
def try_ai_fix( | |
try_to_fix_with_gpt: bool, exception: Exception, json_to_load: str | |
) -> Dict[Any, Any]: | |
"""Try to fix the JSON with the AI | |
Args: | |
try_to_fix_with_gpt (bool): Whether to try to fix the JSON with the AI. | |
exception (Exception): The exception that was raised. | |
json_to_load (str): The JSON string to load. | |
Raises: | |
exception: If try_to_fix_with_gpt is False. | |
Returns: | |
str or dict[Any, Any]: The JSON string or dictionary. | |
""" | |
if not try_to_fix_with_gpt: | |
raise exception | |
if CFG.debug_mode: | |
logger.warn( | |
"Warning: Failed to parse AI output, attempting to fix." | |
"\n If you see this warning frequently, it's likely that" | |
" your prompt is confusing the AI. Try changing it up" | |
" slightly." | |
) | |
# Now try to fix this up using the ai_functions | |
ai_fixed_json = auto_fix_json(json_to_load, JSON_SCHEMA) | |
if ai_fixed_json != "failed": | |
return json.loads(ai_fixed_json) | |
# This allows the AI to react to the error message, | |
# which usually results in it correcting its ways. | |
# logger.error("Failed to fix AI output, telling the AI.") | |
return {} | |
def attempt_to_fix_json_by_finding_outermost_brackets(json_string: str): | |
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.error("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: | |
return {} | |
except (json.JSONDecodeError, ValueError): | |
if CFG.debug_mode: | |
logger.error(f"Error: Invalid JSON: {json_string}\n") | |
if CFG.speak_mode: | |
say_text("Didn't work. I will have to ignore this response then.") | |
logger.error("Error: Invalid JSON, setting it to empty JSON now.\n") | |
json_string = {} | |
return fix_and_parse_json(json_string) | |