diff --git a/libs/core/langchain_core/output_parsers/json.py b/libs/core/langchain_core/output_parsers/json.py index 53c84925055..4f41fb254b2 100644 --- a/libs/core/langchain_core/output_parsers/json.py +++ b/libs/core/langchain_core/output_parsers/json.py @@ -102,12 +102,27 @@ def parse_partial_json(s: str, *, strict: bool = False) -> Any: if is_inside_string: new_s += '"' - # Close any remaining open structures in the reverse order that they were opened. - for closing_char in reversed(stack): - new_s += closing_char + # Try to parse mods of string until we succeed or run out of characters. + while new_s: + final_s = new_s - # Attempt to parse the modified string as JSON. - return json.loads(new_s, strict=strict) + # Close any remaining open structures in the reverse + # order that they were opened. + for closing_char in reversed(stack): + final_s += closing_char + + # Attempt to parse the modified string as JSON. + try: + return json.loads(final_s, strict=strict) + except json.JSONDecodeError: + # If we still can't parse the string as JSON, + # try removing the last character + new_s = new_s[:-1] + + # If we got here, we ran out of characters to remove + # and still couldn't parse the string as JSON, so return the parse error + # for the original string. + return json.loads(s, strict=strict) def parse_json_markdown( diff --git a/libs/core/tests/unit_tests/output_parsers/test_json.py b/libs/core/tests/unit_tests/output_parsers/test_json.py index 2a2c30244e3..3f8ee573b15 100644 --- a/libs/core/tests/unit_tests/output_parsers/test_json.py +++ b/libs/core/tests/unit_tests/output_parsers/test_json.py @@ -199,6 +199,9 @@ TEST_CASES_PARTIAL = [ ('{"foo": "bar", "bar": "foo}', '{"foo": "bar", "bar": "foo}"}'), ('{"foo": "bar", "bar": "foo[', '{"foo": "bar", "bar": "foo["}'), ('{"foo": "bar", "bar": "foo\\"', '{"foo": "bar", "bar": "foo\\""}'), + ('{"foo": "bar", "bar":', '{"foo": "bar"}'), + ('{"foo": "bar", "bar"', '{"foo": "bar"}'), + ('{"foo": "bar", ', '{"foo": "bar"}'), ]