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)