From d351fc550208c0a8db5b9f640be63cfcf4b11238 Mon Sep 17 00:00:00 2001 From: FenjuFu Date: Sun, 4 Oct 2026 16:31:47 +0800 Subject: [PATCH] fix(llm): fall back when a prefilled answer is too deeply nested to parse json.loads raises RecursionError on deeply nested input, which escaped the complete-JSON check and failed the request. Treat it like any other input that is not a complete JSON document. Signed-off-by: FenjuFu --- src/llm/providers/reasoning.py | 6 ++++-- tests/test_reasoning.py | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/llm/providers/reasoning.py b/src/llm/providers/reasoning.py index cbb8b1270..752132292 100644 --- a/src/llm/providers/reasoning.py +++ b/src/llm/providers/reasoning.py @@ -54,13 +54,15 @@ def _is_json_document(content: str) -> bool: """Whether ``content`` is one complete JSON object or array. Reasoning followed by a closing tag is never valid JSON, so a structured - answer that carries the tag inside a string value is left whole. + answer that carries the tag inside a string value is left whole. Input + nested too deeply to parse is not treated as JSON rather than failing the + request. """ stripped = content.strip() if not stripped.startswith(("{", "[")): return False try: json.loads(stripped) - except ValueError: + except (ValueError, RecursionError): return False return True diff --git a/tests/test_reasoning.py b/tests/test_reasoning.py index b2ade433b..3cbee1ef7 100644 --- a/tests/test_reasoning.py +++ b/tests/test_reasoning.py @@ -66,6 +66,13 @@ def test_strip_reasoning_prefilled(content: str, expected: str) -> None: assert strip_reasoning(content, prefilled=True) == expected +def test_strip_reasoning_prefilled_survives_deeply_nested_json() -> None: + # Deep enough for json.loads to raise RecursionError on every supported Python. + depth = 100_000 + content = "[" * depth + '""' + "]" * depth + assert strip_reasoning(content, prefilled=True) == '"' + "]" * depth + + def _openai_provider(content: str, **kwargs: Any) -> OpenAIProvider: provider = OpenAIProvider(api_key="test", **kwargs) message = SimpleNamespace(content=content, tool_calls=None)