7a0da7932b
OSV-Scanner (Scheduled) / scan-scheduled (push) Failing after 0s
Create Release / test-gate (push) Has been cancelled
Create Release / release-gate (push) Has been cancelled
Create Release / ci-gate (push) Has been cancelled
Create Release / version-check (push) Has been cancelled
Create Release / e2e-test-gate (push) Has been cancelled
Create Release / responsive-test-gate (push) Has been cancelled
Create Release / compat-test-gate (push) Has been cancelled
Create Release / compose-integration-gate (push) Has been cancelled
Create Release / vulture-gate (push) Has been cancelled
Create Release / build (push) Has been cancelled
Create Release / provenance (push) Has been cancelled
Create Release / prerelease-docker (push) Has been cancelled
Create Release / publish-docker (push) Has been cancelled
Create Release / create-release (push) Has been cancelled
Create Release / cleanup-changelog (push) Has been cancelled
Create Release / trigger-pypi (push) Has been cancelled
Create Release / monitor-pypi (push) Has been cancelled
Create Release / Clean up orphan prerelease tags and signatures (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-form] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-metrics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [research-workflow] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-core] (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [history-news] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [library] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [link-analytics] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-core] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [chat-lifecycle] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [error-benchmark] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [settings-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) (push) Has been cancelled
Docker Tests (Consolidated) / Accessibility Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Unit Tests (push) Has been cancelled
Docker Tests (Consolidated) / LLM Example Tests (push) Has been cancelled
Docker Tests (Consolidated) / Production Image Smoke Test (push) Has been cancelled
Docker Tests (Consolidated) / Infrastructure Tests (push) Has been cancelled
OSSF Scorecard / OSSF Security Scorecard Analysis (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [mobile] (push) Has been cancelled
Backwards Compatibility / Verify Encryption Constants (push) Has been cancelled
Backwards Compatibility / PyPI Version Compatibility (push) Has been cancelled
Backwards Compatibility / Database Migration Tests (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Docker Tests (Consolidated) / detect-changes (push) Has been cancelled
Docker Tests (Consolidated) / Build Test Image (push) Has been cancelled
Docker Tests (Consolidated) / All Pytest Tests + Coverage (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [accessibility] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [api-crud] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-login] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-pages] (push) Has been cancelled
Docker Tests (Consolidated) / UI Tests (Puppeteer) [auth-register] (push) Has been cancelled
112 lines
4.9 KiB
Python
112 lines
4.9 KiB
Python
"""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}
|