fix(bedrock): handle concatenated JSON in tool call arguments - #20742
Conversation
…-delta-index-mapping fix(responses): preserve tool call argument deltas when streaming id is omitted
add missing indexes on VerificationToken table
When using Bedrock Claude Sonnet 4.5 with tools enabled, the model
sometimes returns multiple tool call arguments as concatenated JSON
objects in a single arguments string, e.g.
'{"command":["curl",...]}{"command":["curl",...]}{"command":["curl",...]}'
json.loads() fails on this with "Extra data", crashing the entire
request in _convert_to_bedrock_tool_call_invoke.
This commit:
- Adds split_concatenated_json_objects() helper in common_utils.py
that uses json.JSONDecoder.raw_decode() to walk a string and extract
each JSON object individually.
- Updates _convert_to_bedrock_tool_call_invoke() to catch JSONDecodeError
and attempt splitting concatenated objects into separate Bedrock
toolUse blocks (first block keeps original ID, subsequent blocks get
suffixed IDs).
- Fixes duplicate json.loads calls and a shadowed 'id' builtin.
- Adds 12 unit tests covering normal, empty, concatenated, and edge cases.
Fixes BerriAI#20543
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile OverviewGreptile SummaryThis PR fixes a critical bug where Bedrock Claude Sonnet 4.5 returns multiple tool call argument objects concatenated in a single string (e.g., Key Changes:
Issues Found:
The fix is well-designed, handles edge cases properly (empty strings, non-dict values, whitespace), and maintains backward compatibility with existing tests. The approach of creating multiple tool blocks with suffixed IDs is a sensible workaround for what appears to be a Bedrock API quirk. Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/litellm_core_utils/prompt_templates/common_utils.py | added split_concatenated_json_objects helper using JSONDecoder.raw_decode() to parse concatenated JSON objects |
| litellm/litellm_core_utils/prompt_templates/factory.py | updated _convert_to_bedrock_tool_call_invoke to handle concatenated JSON by creating multiple toolUse blocks with suffixed IDs; includes style issue with inline import |
Sequence Diagram
sequenceDiagram
participant Caller
participant CTBTI as _convert_to_bedrock_tool_call_invoke
participant JSON as json.loads()
participant Split as split_concatenated_json_objects
participant Decoder as JSONDecoder.raw_decode()
Caller->>CTBTI: tool_calls with arguments
alt arguments is empty/whitespace
CTBTI->>CTBTI: set arguments_dict = {}
else arguments has content
CTBTI->>JSON: parse arguments
alt valid single JSON object
JSON-->>CTBTI: return dict
CTBTI->>CTBTI: create single BedrockToolUseBlock
else JSONDecodeError (concatenated JSON)
JSON--xCTBTI: raise JSONDecodeError
CTBTI->>Split: split_concatenated_json_objects(arguments)
loop for each JSON object in string
Split->>Decoder: raw_decode(raw, idx)
Decoder-->>Split: return (obj, end_idx)
Split->>Split: append obj (or {} if non-dict)
end
Split-->>CTBTI: return list of parsed dicts
alt parsed_objects is not empty
loop for each obj in parsed_objects
CTBTI->>CTBTI: create BedrockToolUseBlock with suffixed ID
end
opt cache_control present
CTBTI->>CTBTI: append cachePoint after last block
end
else parsed_objects is empty
CTBTI->>CTBTI: set arguments_dict = {}
end
end
end
CTBTI-->>Caller: return list of BedrockContentBlock
| from litellm.litellm_core_utils.prompt_templates.common_utils import ( | ||
| split_concatenated_json_objects, | ||
| ) |
There was a problem hiding this comment.
inline import violates style guide (CLAUDE.md:93) - json already imported at top of file
| from litellm.litellm_core_utils.prompt_templates.common_utils import ( | |
| split_concatenated_json_objects, | |
| ) | |
| # split_concatenated_json_objects imported at module level (line ~1-100) |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
* fix(responses): preserve streamed tool deltas when id is omitted
* fix(responses): guard ambiguous tool-call index reuse
* add missing indexes on VerificationToken table
* fix(bedrock): handle concatenated JSON in tool call arguments
When using Bedrock Claude Sonnet 4.5 with tools enabled, the model
sometimes returns multiple tool call arguments as concatenated JSON
objects in a single arguments string, e.g.
'{"command":["curl",...]}{"command":["curl",...]}{"command":["curl",...]}'
json.loads() fails on this with "Extra data", crashing the entire
request in _convert_to_bedrock_tool_call_invoke.
This commit:
- Adds split_concatenated_json_objects() helper in common_utils.py
that uses json.JSONDecoder.raw_decode() to walk a string and extract
each JSON object individually.
- Updates _convert_to_bedrock_tool_call_invoke() to catch JSONDecodeError
and attempt splitting concatenated objects into separate Bedrock
toolUse blocks (first block keeps original ID, subsequent blocks get
suffixed IDs).
- Fixes duplicate json.loads calls and a shadowed 'id' builtin.
- Adds 12 unit tests covering normal, empty, concatenated, and edge cases.
Fixes #20543
---------
Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com>
Co-authored-by: Sameer Kankute <sameer@berri.ai>
Co-authored-by: Carlo Alberto Ferraris <cafxx@mercari.com>
…I#20742) * fix(responses): preserve streamed tool deltas when id is omitted * fix(responses): guard ambiguous tool-call index reuse * add missing indexes on VerificationToken table * fix(bedrock): handle concatenated JSON in tool call arguments When using Bedrock Claude Sonnet 4.5 with tools enabled, the model sometimes returns multiple tool call arguments as concatenated JSON objects in a single arguments string, e.g. '{"command":["curl",...]}{"command":["curl",...]}{"command":["curl",...]}' json.loads() fails on this with "Extra data", crashing the entire request in _convert_to_bedrock_tool_call_invoke. This commit: - Adds split_concatenated_json_objects() helper in common_utils.py that uses json.JSONDecoder.raw_decode() to walk a string and extract each JSON object individually. - Updates _convert_to_bedrock_tool_call_invoke() to catch JSONDecodeError and attempt splitting concatenated objects into separate Bedrock toolUse blocks (first block keeps original ID, subsequent blocks get suffixed IDs). - Fixes duplicate json.loads calls and a shadowed 'id' builtin. - Adds 12 unit tests covering normal, empty, concatenated, and edge cases. Fixes BerriAI#20543 --------- Co-authored-by: Emerson Gomes <emerson.gomes@thalesgroup.com> Co-authored-by: Sameer Kankute <sameer@berri.ai> Co-authored-by: Carlo Alberto Ferraris <cafxx@mercari.com>
Relevant issues
Fixes #20543
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
tests/litellm/directory, Adding at least 1 test is a hard requirement - see detailsmake test-unitType
🐛 Bug Fix
Changes
Problem
When using Bedrock Claude Sonnet 4.5 with tools enabled, the model sometimes returns multiple tool call argument objects concatenated in a single
argumentsstring:json.loads()fails withJSONDecodeError: Extra data, which crashes the entire request in_convert_to_bedrock_tool_call_invoke()and surfaces as:A previous fix attempt (PR #19198) was reverted (PR #19243) because it broke existing tests.
Solution
1. New helper:
split_concatenated_json_objects()(common_utils.py)Uses
json.JSONDecoder.raw_decode()to walk a string containing one or more concatenated JSON objects and extract each one individually. This is a robust, incremental parser that handles:{}per Bedrock'stoolUse.inputrequirement)2. Updated
_convert_to_bedrock_tool_call_invoke()(factory.py)json.loads()as before (happy path untouched)JSONDecodeError: falls back tosplit_concatenated_json_objects()to split concatenated arguments into separateBedrockToolUseBlockentries{original_id}_1,{original_id}_2, ...)cache_controlis attached after the last split blockjson.loads()calls and shadowedidbuiltinTests Added (12 total)
test_litellm_core_utils_prompt_templates_common_utils.py(6 tests):test_split_concatenated_json_single_object- normal single objecttest_split_concatenated_json_multiple_objects- exact reproduction of [Bug]: Bedrock Claude Sonnet 4.5 returns concatenated JSON in tool call arguments causing "Extra data" error #20543test_split_concatenated_json_with_whitespace- whitespace between objectstest_split_concatenated_json_empty_string- empty/whitespace inputtest_split_concatenated_json_non_dict_value- non-dict values →{}test_split_concatenated_json_invalid_raises- truly invalid JSON raisestest_litellm_core_utils_prompt_templates_factory.py(6 tests):test_bedrock_tool_call_invoke_normal_single_tool- normal happy pathtest_bedrock_tool_call_invoke_empty_arguments- empty args →{}test_bedrock_tool_call_invoke_concatenated_json- core fix verification: 3 concatenated objects → 3 separate toolUse blocks with correct IDstest_bedrock_tool_call_invoke_concatenated_json_with_cache_control- cache_control with splittest_bedrock_tool_call_invoke_non_dict_arguments-'""'→{}test_bedrock_tool_call_invoke_multiple_normal_tools- parallel tool calls