diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index 709e24bfc..bf991233c 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -77,7 +77,7 @@ class Action(ABC): logger.debug(content) output_class = ActionOutput.create_model_class(output_class_name, output_data_mapping) - pattern = r"\[CONTENT\](.*?)\[/CONTENT\]" + pattern = r"\[CONTENT\](.+?)\[/CONTENT\]" # Use re.findall to extract content between the tags extracted_content = re.search(pattern, content, re.DOTALL).group(1) diff --git a/metagpt/actions/write_prd.py b/metagpt/actions/write_prd.py index d2bb11eb8..f259e786f 100644 --- a/metagpt/actions/write_prd.py +++ b/metagpt/actions/write_prd.py @@ -68,8 +68,13 @@ FORMAT_EXAMPLE = """ { "Original Requirements": "", "Search Information": "", - "mermaid quadrantChart code": ' - quadrantChart + + "Role": "You are a professional product manager; the goal is to design a concise, usable, efficient product", + "Requirements": "", + "Product Goals": [], + "User Stories": [], + "Competitive Analysis": [], + "Competitive Quadrant Chart": "quadrantChart title Reach and engagement of campaigns x-axis Low Reach --> High Reach y-axis Low Engagement --> High Engagement @@ -82,17 +87,7 @@ FORMAT_EXAMPLE = """ Campaign C: [0.57, 0.69] Campaign D: [0.78, 0.34] Campaign E: [0.40, 0.34] - Campaign F: [0.35, 0.78] - ' - , - - }, - "Role": "You are a professional product manager; the goal is to design a concise, usable, efficient product", - "Requirements": "", - "Product Goals": [], - "User Stories": [], - "Competitive Analysis": [], - "Competitive Quadrant Chart": "", + Campaign F: [0.35, 0.78]", "Requirement Analysis": "", "Requirement Pool": [["P0","P0 requirement"],["P1","P1 requirement"]], "UI Design draft": "", diff --git a/metagpt/utils/custom_decoder.py b/metagpt/utils/custom_decoder.py index 02a8ab41c..fcac85963 100644 --- a/metagpt/utils/custom_decoder.py +++ b/metagpt/utils/custom_decoder.py @@ -26,7 +26,12 @@ def py_make_scanner(context): raise StopIteration(idx) from None if nextchar == '"' or nextchar == "'": - return parse_string(string, idx + 1, strict, delimiter=nextchar) + if idx + 2 < len(string) and string[idx + 1] == nextchar and string[idx + 2] == nextchar: + # Handle the case where the next two characters are the same as nextchar + return parse_string(string, idx + 3, strict, delimiter=nextchar * 3) # triple quote + else: + # Handle the case where the next two characters are not the same as nextchar + return parse_string(string, idx + 1, strict, delimiter=nextchar) elif nextchar == "{": return parse_object((string, idx + 1), strict, _scan_once, object_hook, object_pairs_hook, memo) elif nextchar == "[": @@ -67,6 +72,8 @@ def py_make_scanner(context): FLAGS = re.VERBOSE | re.MULTILINE | re.DOTALL STRINGCHUNK = re.compile(r'(.*?)(["\\\x00-\x1f])', FLAGS) STRINGCHUNK_SINGLEQUOTE = re.compile(r"(.*?)([\'\\\x00-\x1f])", FLAGS) +STRINGCHUNK_TRIPLE_DOUBLE_QUOTE = re.compile(r"(.*?)(\"\"\"|[\\\x00-\x1f])", FLAGS) +STRINGCHUNK_TRIPLE_SINGLEQUOTE = re.compile(r"(.*?)('''|[\\\x00-\x1f])", FLAGS) BACKSLASH = { '"': '"', "\\": "\\", @@ -112,7 +119,12 @@ def JSONObject( raise JSONDecodeError("Expecting property name enclosed in double quotes", s, end) end += 1 while True: - key, end = scanstring(s, end, strict, delimiter=nextchar) + if end + 1 < len(s) and s[end] == nextchar and s[end + 1] == nextchar: + # Handle the case where the next two characters are the same as nextchar + key, end = scanstring(s, end + 2, strict, delimiter=nextchar * 3) + else: + # Handle the case where the next two characters are not the same as nextchar + key, end = scanstring(s, end, strict, delimiter=nextchar) key = memo_get(key, key) # To skip some function call overhead we optimize the fast paths where # the JSON key separator is ": " or just ":". @@ -176,8 +188,12 @@ def py_scanstring(s, end, strict=True, _b=BACKSLASH, _m=STRINGCHUNK.match, delim begin = end - 1 if delimiter == '"': _m = STRINGCHUNK.match - else: + elif delimiter == "'": _m = STRINGCHUNK_SINGLEQUOTE.match + elif delimiter == '"""': + _m = STRINGCHUNK_TRIPLE_DOUBLE_QUOTE.match + else: + _m = STRINGCHUNK_TRIPLE_SINGLEQUOTE.match while 1: chunk = _m(s, end) if chunk is None: diff --git a/tests/metagpt/utils/test_custom_decoder.py b/tests/metagpt/utils/test_custom_decoder.py index 99ca3fc6a..c7b14ad59 100644 --- a/tests/metagpt/utils/test_custom_decoder.py +++ b/tests/metagpt/utils/test_custom_decoder.py @@ -36,3 +36,37 @@ def test_parse_single_quote(): parsed_data = decoder.decode(input_data) assert 'a"\n b' in parsed_data + + +def test_parse_triple_double_quote(): + # Create a custom JSON decoder + decoder = CustomDecoder(strict=False) + # Your provided input with single-quoted strings and line breaks + input_data = '{"""a""":"b"}' + # Parse the JSON using the custom decoder + + parsed_data = decoder.decode(input_data) + assert "a" in parsed_data + + input_data = '{"""a""":"""b"""}' + # Parse the JSON using the custom decoder + + parsed_data = decoder.decode(input_data) + assert parsed_data["a"] == "b" + + +def test_parse_triple_single_quote(): + # Create a custom JSON decoder + decoder = CustomDecoder(strict=False) + # Your provided input with single-quoted strings and line breaks + input_data = "{'''a''':'b'}" + # Parse the JSON using the custom decoder + + parsed_data = decoder.decode(input_data) + assert "a" in parsed_data + + input_data = "{'''a''':'''b'''}" + # Parse the JSON using the custom decoder + + parsed_data = decoder.decode(input_data) + assert parsed_data["a"] == "b"