|
"""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. |
|
""" |
|
|
|
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 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) |
|
return result_string |
|
except json.JSONDecodeError: |
|
|
|
|
|
|
|
|
|
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. |
|
""" |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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." |
|
) |
|
|
|
ai_fixed_json = auto_fix_json(json_to_load, JSON_SCHEMA) |
|
|
|
if ai_fixed_json != "failed": |
|
return json.loads(ai_fixed_json) |
|
|
|
|
|
|
|
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: |
|
|
|
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) |
|
|