Skip to content

fix(converter): don't let trailing braces corrupt valid JSON extraction#6521

Open
ErenAta16 wants to merge 2 commits into
crewAIInc:mainfrom
ErenAta16:fix/json-extraction-trailing-braces
Open

fix(converter): don't let trailing braces corrupt valid JSON extraction#6521
ErenAta16 wants to merge 2 commits into
crewAIInc:mainfrom
ErenAta16:fix/json-extraction-trailing-braces

Conversation

@ErenAta16

Copy link
Copy Markdown

_JSON_PATTERN = re.compile(r"({.*})", re.DOTALL) in converter.py greedily matches from the first { to the last } anywhere in the string. If the LLM's output contains a complete, valid JSON object followed by commentary that itself references curly braces - mentioning the schema, a code snippet, a set literal, anything - the match swallows that trailing text too, and json.loads fails on the resulting garbage even though the actual JSON object was perfectly well formed:

result = '''Here is the extracted data:
{"name": "Alice", "age": 30}

Note: this response follows the requested schema (see the format described as {"type": "object"} in the task).'''

match = _JSON_PATTERN.search(result)
json.loads(match.group())
# json.decoder.JSONDecodeError: Extra data: line 3 column 1 (char 30)

In handle_partial_json, that JSONDecodeError triggers an immediate fall through to convert_with_instructions (a re-prompt), discarding output that was already correct.

This is a different failure mode from #5460 (closed via #5461), which was about the regex false-positively matching brace-delimited text that isn't JSON at all (e.g. GraphQL schemas). This one is a false-negative: genuinely valid JSON gets corrupted because of what comes after it.

Fix

Replaced the regex-based extraction with json.JSONDecoder(strict=False).raw_decode(result, start), starting at the first {. raw_decode parses exactly one JSON value and stops there, so trailing braces elsewhere in the string can't corrupt an otherwise-valid match. The GraphQL fall-through from #5461 is preserved, since raw_decode still raises JSONDecodeError on non-JSON brace-delimited content for the same reason json.loads did.

Applied to both handle_partial_json and async_handle_partial_json, which shared the identical pattern. Also drops the now-unused re import and the _JSON_PATTERN constant.

Testing

Added test_handle_partial_json_ignores_braces_in_trailing_commentary to tests/utilities/test_converter.py, covering the exact repro above.

Ran the full tests/utilities/test_converter.py suite: 51/52 passing. The one failure (test_supports_function_calling_false) is pre-existing and unrelated - it needs the optional litellm package, which isn't installed in my sandbox, and I confirmed it fails identically on unpatched main before making any changes.

I did try a second test with brace-containing text on both sides of the target object, expecting it to also resolve correctly - it doesn't, because raw_decode starting at the first { will happily parse an earlier, self-contained JSON-looking object if one exists before the real one. That's a real limitation, but a different (arguably harder - which object is "the" answer?) problem than what this PR fixes, so I removed that test rather than either overclaiming or scope-creeping the fix. Happy to discuss if that's worth a follow-up.

_JSON_PATTERN = re.compile(r"({.*})", re.DOTALL) greedily matched from
the first '{' to the LAST '}' anywhere in the string. If the LLM's
output contained a complete, valid JSON object followed by any
commentary that itself referenced curly braces (e.g. mentioning the
schema, a code snippet, or a set literal), the match swallowed that
trailing text too and json.loads failed on the resulting garbage, even
though the actual JSON object was perfectly well formed.

Replaced the regex-based extraction with json.JSONDecoder.raw_decode
starting at the first '{': it parses exactly one JSON value and stops
there regardless of what follows, so trailing braces elsewhere in the
string can no longer corrupt an otherwise-valid match. This preserves
the existing fall-through behavior for brace-delimited non-JSON
content (e.g. GraphQL schemas, fixed by crewAIInc#5461/crewAIInc#5460) since raw_decode
still raises JSONDecodeError on those.

Applied to both handle_partial_json and async_handle_partial_json,
which shared the same pattern. Also drops the now-unused re import.

Verified against the existing test suite (51/52 passing; the one
failure is a pre-existing, unrelated missing-litellm-dependency issue
in my sandbox, reproduced identically on unpatched main).
@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8355723a-f970-4a4a-a3df-b6ed95377649

📥 Commits

Reviewing files that changed from the base of the PR and between 6dbc13c and b15a1ed.

📒 Files selected for processing (1)
  • lib/crewai/tests/utilities/test_converter.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • lib/crewai/tests/utilities/test_converter.py

📝 Walkthrough

Walkthrough

Changes

Partial JSON Parsing

Layer / File(s) Summary
Deterministic JSON extraction
lib/crewai/src/crewai/utilities/converter.py
Replaces regex-based extraction with a helper that finds the first { and decodes one JSON value using JSONDecoder.raw_decode.
Sync and async partial JSON integration
lib/crewai/src/crewai/utilities/converter.py, lib/crewai/tests/utilities/test_converter.py
Updates both handlers to use the helper and adds synchronous and asynchronous coverage for braces in trailing commentary.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: fixing JSON extraction when trailing braces appear in commentary.
Description check ✅ Passed The description is directly about the converter JSON extraction bug and the implemented fix, so it matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
lib/crewai/tests/utilities/test_converter.py (1)

228-242: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider adding an async equivalent test.

Both handle_partial_json and async_handle_partial_json now share _extract_first_json_object, so the extraction logic is covered. However, there's no async test for the trailing-commentary scenario. Adding one would provide symmetric coverage for the async path, which the PR objectives explicitly mention.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/crewai/tests/utilities/test_converter.py` around lines 228 - 242, Add an
async counterpart to
test_handle_partial_json_ignores_braces_in_trailing_commentary, using the same
JSON input with brace-containing trailing commentary and awaiting
async_handle_partial_json. Assert the returned value is a SimpleModel with the
expected name and age to provide equivalent coverage for the async path.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@lib/crewai/tests/utilities/test_converter.py`:
- Around line 228-242: Add an async counterpart to
test_handle_partial_json_ignores_braces_in_trailing_commentary, using the same
JSON input with brace-containing trailing commentary and awaiting
async_handle_partial_json. Assert the returned value is a SimpleModel with the
expected name and age to provide equivalent coverage for the async path.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9ecb18e7-4a39-4ab6-abfe-3bfed1d78a33

📥 Commits

Reviewing files that changed from the base of the PR and between fb8e93b and 6dbc13c.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/utilities/converter.py
  • lib/crewai/tests/utilities/test_converter.py

Addresses CodeRabbit nitpick on crewAIInc#6521: async_handle_partial_json shares
_extract_first_json_object with the sync path, so it should get the
same regression coverage for the trailing-commentary bug.
@ErenAta16

Copy link
Copy Markdown
Author

Fair point - added test_async_handle_partial_json_ignores_braces_in_trailing_commentary alongside the sync one, since both paths share _extract_first_json_object. Full suite still at 52/53 (same pre-existing unrelated litellm-dependency failure as before).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant