diff --git a/dev_gpt/options/generate/chains/fix_based_on_error.py b/dev_gpt/options/generate/chains/fix_based_on_error.py new file mode 100644 index 0000000..5d34cc4 --- /dev/null +++ b/dev_gpt/options/generate/chains/fix_based_on_error.py @@ -0,0 +1,32 @@ +import traceback + + + +def fix_based_on_error_chain(context_string, content_type, original_content, parser): + from dev_gpt.apis.gpt import ask_gpt + current_content = original_content + for i in range(3): + try: + return parser(current_content) + except Exception: + error_message = traceback.format_exc() + current_content = ask_gpt(fix_based_on_error_prompt, context_string=context_string, content_type=content_type, current_content=current_content, error_message=error_message) + raise Exception(f'Could not fix the content:\n{original_content}') + + +fix_based_on_error_prompt = '''\ +Context: +{context_string} + +Original {content_type}: +{current_content} + +Error message: +{error_message} + +Your task: +You must return the fixed {content_type}. +Most importantly, you are not allowed to return something else - only the fixed {content_type}. +''' + + diff --git a/dev_gpt/options/generate/generator.py b/dev_gpt/options/generate/generator.py index 9348236..8ac2ca9 100644 --- a/dev_gpt/options/generate/generator.py +++ b/dev_gpt/options/generate/generator.py @@ -21,6 +21,7 @@ from dev_gpt.constants import FILE_AND_TAG_PAIRS, NUM_IMPLEMENTATION_STRATEGIES, IMPLEMENTATION_FILE_TAG, LANGUAGE_PACKAGES, UNNECESSARY_PACKAGES, DOCKER_BASE_IMAGE_VERSION, SEARCH_PACKAGES, \ INDICATOR_TO_IMPORT_STATEMENT from dev_gpt.options.generate.conversation_logger import Timer +from dev_gpt.options.generate.parser import json_parser from dev_gpt.options.generate.pm.pm import PM from dev_gpt.options.generate.templates_user import template_generate_microservice_name, \ template_generate_possible_packages, \ @@ -439,8 +440,7 @@ pytest self.previous_solutions.append(suggested_solution) def generate_solution_suggestion(self, summarized_error, all_files_string): - suggested_solutions = json.loads( - self.generate_and_persist_file( + json_string = self.generate_and_persist_file( section_title='Suggest solution for code issue', template=template_suggest_solutions_code_issue, file_name_s=['solutions.json'], @@ -449,8 +449,9 @@ pytest test_description=self.microservice_specification.test, all_files_string=all_files_string, response_format_example=response_format_suggest_solutions, - )['solutions.json'] - ) + ) + + suggested_solutions = json_parser(json_string)['solutions.json'] if len(self.previous_errors) > 0: was_error_seen_before = json.loads( diff --git a/dev_gpt/options/generate/parser.py b/dev_gpt/options/generate/parser.py index 354bc37..a0fe79b 100644 --- a/dev_gpt/options/generate/parser.py +++ b/dev_gpt/options/generate/parser.py @@ -1,6 +1,8 @@ import json import re +from dev_gpt.options.generate.chains.fix_based_on_error import fix_based_on_error_chain + def identity_parser(x): return x @@ -16,6 +18,10 @@ def boolean_parser(x): def json_parser(x): if '```' in x: - pattern = r'```(.+)```' - x = re.findall(pattern, x, re.DOTALL)[-1] + pattern = r'```(json)?(.+)```' + x = re.findall(pattern, x, re.DOTALL)[-1][-1] return json.loads(x) + +def self_healing_json_parser(original_json_string): + return fix_based_on_error_chain('I want to load my JSON string using json.loads(x) but get the following error:', 'JSON', + original_json_string, parser=json_parser) diff --git a/dev_gpt/options/generate/templates_user.py b/dev_gpt/options/generate/templates_user.py index 818b743..786f33e 100644 --- a/dev_gpt/options/generate/templates_user.py +++ b/dev_gpt/options/generate/templates_user.py @@ -310,14 +310,22 @@ Output the apt-get packages that need to be placed at {{APT_GET_PACKAGES}} as js ```json {{"packages": ["", ""]}} ``` -Example: -Error is about missing package `libgl1-mesa-glx`. -The output is: + +Example output for an error is about missing package `libgl1-mesa-glx`: **apt-get-packages.json** ```json {{"packages": [libgl1-mesa-glx]}} ``` -Only output content of the apt-get-packages.json file. Ensure the response can be parsed by Python json.loads + +Negative example1: +```json +{{"packages": [libgl1-mesa-glx]}} +``` + +Negative example2: +{{"packages": [libgl1-mesa-glx]}} + +Note: Only output content of the apt-get-packages.json file. Note: you must not output the content of any other. Especially don't output the Dockerfile or requirements.txt. Note: the first line you output must be: **apt-get-packages.json** ''' diff --git a/test/unit/test_response_parsing.py b/test/unit/test_response_parsing.py index 6fea996..33d4102 100644 --- a/test/unit/test_response_parsing.py +++ b/test/unit/test_response_parsing.py @@ -1,6 +1,11 @@ +import os + import pytest +from dev_gpt.apis.gpt import GPTSession from dev_gpt.options.generate.generator import Generator +from dev_gpt.options.generate.parser import self_healing_json_parser + def create_code_block(with_backticks, asterisks, with_highlight_info, file_name, start_inline, content): code_block = f''' @@ -42,3 +47,21 @@ def test_extract_content_from_result(plain_text, expected1, expected2): parsed_result2 = Generator.extract_content_from_result(plain_text, 'test100.json', True, False) assert parsed_result2 == expected2 + +def test_self_healing_json_parser(tmpdir): + os.environ['VERBOSE'] = 'true' + GPTSession(os.path.join(str(tmpdir), 'log.json'), model='gpt-3.5-turbo') + json_response = '''\ +solutions.json +```json +{ + "1": "Change line 7 of microservice.py to 'pdf_file = input_dict['pdf_file'].encode('latin-1')' to convert the bytes object to a string before passing it to PyPDF2.", + "2": "Change line 7 of microservice.py to 'pdf_file = input_dict['pdf_file'].decode('utf-8')' to decode the bytes object to a string before passing it to PyPDF2.", + "3": "Change line 13 of test_microservice.py to 'input_dict = {"pdf_file": 'Sample PDF file content'.encode('latin-1')}' to encode the string to a bytes object before passing it to func.", + "4": "Change line 13 of test_microservice.py to 'input_dict = {"pdf_file": 'Sample PDF file content'.decode('utf-8')}' to decode the string to a bytes object before passing it to func." +} +```''' + parsed_json = self_healing_json_parser(json_response) + for key in ['1', '2', '3', '4']: + assert key in parsed_json + assert 'Change' in parsed_json[key] \ No newline at end of file