fix(core): raise OutputParserException for non-dict JSON outputs (#32236)

**Description:**
Raise a more descriptive OutputParserException when JSON parsing results
in a non-dict type. This improves debugging and aligns behavior with
expectations when using expected_keys.

**Issue:**
Fixes #32233

**Twitter handle:**
@yashvtobre

**Testing:**

- Ran make format and make lint from the root directory; both passed
cleanly.
- Attempted make test but no such target exists in the root Makefile.
- Executed tests directly via pytest targeting the relevant test file,
confirming all tests pass except for unrelated async test failures
outside the scope of this change.

**Notes:**

- No additional dependencies introduced.
- Changes are backward compatible and isolated within the output parser
module.

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Mason Daugherty <mason@langchain.dev>
Co-authored-by: Mason Daugherty <github@mdrxy.com>
This commit is contained in:
Yash Vishwanath Tobre
2025-09-10 19:57:09 -05:00
committed by GitHub
parent 7a158c7f1c
commit a8828b1bda
2 changed files with 19 additions and 2 deletions

View File

@@ -190,6 +190,12 @@ def parse_and_check_json_markdown(text: str, expected_keys: list[str]) -> dict:
except json.JSONDecodeError as e: except json.JSONDecodeError as e:
msg = f"Got invalid JSON object. Error: {e}" msg = f"Got invalid JSON object. Error: {e}"
raise OutputParserException(msg) from e raise OutputParserException(msg) from e
if not isinstance(json_obj, dict):
error_message = (
f"Expected JSON object (dict), but got: {type(json_obj).__name__}. "
)
raise OutputParserException(error_message, llm_output=text)
for key in expected_keys: for key in expected_keys:
if key not in json_obj: if key not in json_obj:
msg = ( msg = (

View File

@@ -10,7 +10,11 @@ from langchain_core.output_parsers.json import (
SimpleJsonOutputParser, SimpleJsonOutputParser,
) )
from langchain_core.utils.function_calling import convert_to_openai_function from langchain_core.utils.function_calling import convert_to_openai_function
from langchain_core.utils.json import parse_json_markdown, parse_partial_json from langchain_core.utils.json import (
parse_and_check_json_markdown,
parse_json_markdown,
parse_partial_json,
)
from tests.unit_tests.pydantic_utils import _schema from tests.unit_tests.pydantic_utils import _schema
GOOD_JSON = """```json GOOD_JSON = """```json
@@ -204,13 +208,20 @@ def test_parse_json_with_part_code_blocks() -> None:
def test_parse_json_with_code_blocks_and_newlines() -> None: def test_parse_json_with_code_blocks_and_newlines() -> None:
parsed = parse_json_markdown(JSON_WITH_MARKDOWN_CODE_BLOCK_AND_NEWLINES) parsed = parse_json_markdown(JSON_WITH_MARKDOWN_CODE_BLOCK_AND_NEWLINES)
assert parsed == { assert parsed == {
"action": "Final Answer", "action": "Final Answer",
"action_input": '```bar\n<div id="1" class="value">\n\ttext\n</div>```', "action_input": '```bar\n<div id="1" class="value">\n\ttext\n</div>```',
} }
def test_parse_non_dict_json_output() -> None:
text = "```json\n1\n```"
with pytest.raises(OutputParserException) as exc_info:
parse_and_check_json_markdown(text, expected_keys=["foo"])
assert "Expected JSON object (dict)" in str(exc_info.value)
TEST_CASES_ESCAPED_QUOTES = [ TEST_CASES_ESCAPED_QUOTES = [
JSON_WITH_ESCAPED_DOUBLE_QUOTES_IN_NESTED_JSON, JSON_WITH_ESCAPED_DOUBLE_QUOTES_IN_NESTED_JSON,
] ]