"""Tests for real-world LLM output patterns that extract_json encounters in production. These tests document edge cases observed in actual LLM responses that aren't covered by the basic test_json_utils.py tests. """ from local_deep_research.utilities.json_utils import ( extract_json, get_llm_response_text, ) class TestMultipleCodeFences: """Tests for LLM responses containing multiple code fences.""" def test_multiple_json_code_fences_uses_first(self): """When an LLM self-corrects by producing two ```json blocks, _strip_code_fences uses the first one. This documents the behavior since it could arguably pick either.""" text = ( "Let me try:\n" '```json\n{"answer": "first attempt"}\n```\n' "Wait, let me correct that:\n" '```json\n{"answer": "corrected"}\n```' ) result = extract_json(text) # Documents current behavior: first ```json block wins assert result == {"answer": "first attempt"} def test_non_json_language_tag_code_fence(self): """A ```python fence doesn't match ```json, so it falls through to the bare ``` path which extracts between the first pair of ```.""" text = ( "Here is the data:\n" '```python\ndata = {"key": "value"}\n```\n' "That's it." ) result = extract_json(text) # The bare ``` path extracts: 'python\ndata = {"key": "value"}' # Then bracket extraction finds {"key": "value"} inside assert result == {"key": "value"} class TestUrlsAndCommentRegex: """Tests for interaction between URLs in JSON values and the // comment removal regex.""" def test_malformed_json_with_urls_comment_regex_interaction(self): """When JSON has a trailing comma (triggering artifact cleaning), the // comment-removal regex strips everything after // in URLs. This documents a known limitation.""" text = '{"url": "https://example.com/page", "count": 1,}' result = extract_json(text) # The trailing comma makes direct parse fail. # Artifact cleaning: removes trailing comma AND removes "//example.com/page" # After cleaning: {"url": "https:, "count": 1} -- which is invalid JSON # This is a known limitation: malformed JSON + URLs = potential breakage # The result depends on whether the cleaned version happens to parse # The important thing is it doesn't crash assert result is None or isinstance(result, (dict, list)) def test_valid_json_with_double_slash_in_string_parses_directly(self): """Valid JSON with // in string values parses on the direct parse step and never hits artifact cleaning, so URLs are preserved.""" text = '{"url": "https://example.com/path", "title": "Test"}' result = extract_json(text) assert result == {"url": "https://example.com/path", "title": "Test"} class TestContentAttributeFormats: """Tests for different LangChain content attribute formats.""" def test_content_attribute_is_list_of_content_blocks(self): """LangChain multimodal AIMessage can have content as a list of blocks. get_llm_response_text extracts the text from the text blocks (joining them, dropping non-text blocks like image_url) — not the list repr.""" class FakeMessage: content = [ {"type": "text", "text": '{"key": "value"}'}, {"type": "image_url", "image_url": "data:..."}, ] text = get_llm_response_text(FakeMessage()) # The text block is extracted cleanly; the image_url block is dropped. assert text == '{"key": "value"}' # Not the list's Python repr, and no non-text block content leaks. assert "image_url" not in text assert "data:" not in text class TestBracketMismatchEdgeCases: """Tests for bracket extraction edge cases with multiple JSON objects.""" def test_bracket_mismatch_across_separate_json_objects(self): """Text like '{"a": 1} middle {"b": 2}' - rfind grabs the last }, so extraction spans from first { to last }, giving '{"a": 1} middle {"b": 2}' which isn't valid JSON.""" text = 'Here is {"a": 1} and also {"b": 2} done' result = extract_json(text) # The bracket extraction grabs from first { to last }: # '{"a": 1} and also {"b": 2}' # This isn't valid JSON, so it fails. Artifact cleaning won't help. # Result should be None since the combined span isn't valid JSON. assert result is None def test_single_json_object_in_prose_succeeds(self): """Single JSON object in prose works because find/rfind grab matching brackets correctly.""" text = 'The result is {"status": "ok", "count": 42} as expected.' result = extract_json(text) assert result == {"status": "ok", "count": 42}