fix(bedrock): don't raise a retryable error on malformed tool call arguments

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Devin AI 2026-07-31 02:39:24 +00:00
parent 71b825a7f0
commit 7f18a84bcd
2 changed files with 49 additions and 3 deletions

View file

@ -3682,7 +3682,10 @@ def _convert_to_bedrock_tool_call_invoke(
# '{"cmd":"a"}{"cmd":"b"}{"cmd":"c"}'
# Split them and emit one toolUse block per object.
# Fixes: https://github.com/BerriAI/litellm/issues/20543
parsed_objects = split_concatenated_json_objects(arguments)
try:
parsed_objects = split_concatenated_json_objects(arguments)
except json.JSONDecodeError:
parsed_objects = []
if parsed_objects:
# First object keeps the original tool id.
for obj_idx, obj in enumerate(parsed_objects):
@ -3718,8 +3721,13 @@ def _convert_to_bedrock_tool_call_invoke(
_parts_list.append(cache_point_block)
return _parts_list
except Exception as e:
raise Exception(
"Unable to convert openai tool calls={} to bedrock tool calls. Received error={}".format(tool_calls, str(e))
tool_call_ids = [tool.get("id") for tool in tool_calls if isinstance(tool, dict)]
raise litellm.BadRequestError(
message="Unable to convert openai tool calls with ids={} to bedrock tool calls. Received error={}".format(
tool_call_ids, str(e)
),
model=model or "",
llm_provider="bedrock",
)

View file

@ -2286,6 +2286,44 @@ def test_bedrock_tool_call_invoke_non_dict_arguments():
assert result[0]["toolUse"]["input"] == {}
def test_bedrock_tool_call_invoke_truncated_json_arguments():
"""
Truncated tool call arguments (issue #35303) must not raise. A client replaying a
partially streamed tool call would otherwise trigger a pre-network exception that the
router maps to a retryable APIConnectionError and retries through the fallback graph.
"""
tool_calls = [
{
"id": "tooluse_MAh2QLVjBRkvi5QJkLQ08V",
"type": "function",
"function": {
"name": "replace_note_content",
"arguments": '{"note_id": "999af35c-4061-4ece-8581-7d43fc988ba4", "title": "WG"',
},
}
]
result = _convert_to_bedrock_tool_call_invoke(tool_calls)
assert len(result) == 1
assert result[0]["toolUse"]["toolUseId"] == "tooluse_MAh2QLVjBRkvi5QJkLQ08V"
assert result[0]["toolUse"]["input"] == {}
def test_bedrock_tool_call_invoke_unconvertible_raises_non_retryable_bad_request():
"""
Conversion failures are client input errors, so they must surface as a non-retryable
BadRequestError instead of a bare Exception that maps to APIConnectionError, and the
message must not embed the tool call payload (issue #35303).
"""
tool_calls = [{"id": "call_bad", "type": "function", "function": None}]
with pytest.raises(litellm.BadRequestError) as exc_info:
_convert_to_bedrock_tool_call_invoke(tool_calls)
assert exc_info.value.status_code == 400
assert "call_bad" in str(exc_info.value)
assert "function" not in str(exc_info.value).split("Received error=")[0]
def test_make_valid_bedrock_tool_name_preserves_hyphens():
assert make_valid_bedrock_tool_name("my-tool") == "my-tool"
assert (