diff --git a/src/strands/event_loop/streaming.py b/src/strands/event_loop/streaming.py index 0a11611354..146e899f60 100644 --- a/src/strands/event_loop/streaming.py +++ b/src/strands/event_loop/streaming.py @@ -279,6 +279,11 @@ def handle_content_block_stop(state: dict[str, Any]) -> dict[str, Any]: try: current_tool_use["input"] = json.loads(current_tool_use["input"]) except ValueError: + logger.warning( + "tool_name=<%s>, raw_input=<%s> | failed to parse tool input json, defaulting to empty dict", + current_tool_use.get("name", "unknown"), + current_tool_use["input"][:200] if isinstance(current_tool_use.get("input"), str) else "", + ) current_tool_use["input"] = {} tool_use_id = current_tool_use["toolUseId"] diff --git a/tests/strands/event_loop/test_streaming.py b/tests/strands/event_loop/test_streaming.py index 93f8d95f82..57ef8a2e57 100644 --- a/tests/strands/event_loop/test_streaming.py +++ b/tests/strands/event_loop/test_streaming.py @@ -366,6 +366,25 @@ def test_handle_content_block_delta(event: ContentBlockDeltaEvent, event_type, s "redactedContent": b"", }, ), + # Tool Use - Malformed input JSON + ( + { + "content": [], + "current_tool_use": {"toolUseId": "123", "name": "test", "input": "{invalid json}"}, + "text": "", + "reasoningText": "", + "citationsContent": [], + "redactedContent": b"", + }, + { + "content": [{"toolUse": {"toolUseId": "123", "name": "test", "input": {}}}], + "current_tool_use": {}, + "text": "", + "reasoningText": "", + "citationsContent": [], + "redactedContent": b"", + }, + ), # Text ( { @@ -527,6 +546,25 @@ def test_handle_content_block_stop(state, exp_updated_state): assert tru_updated_state == exp_updated_state +@unittest.mock.patch("strands.event_loop.streaming.logger") +def test_handle_content_block_stop_logs_warning_on_malformed_json(mock_logger): + state = { + "content": [], + "current_tool_use": {"toolUseId": "123", "name": "test_tool", "input": "{invalid json}"}, + "text": "", + "reasoningText": "", + "citationsContent": [], + "redactedContent": b"", + } + + strands.event_loop.streaming.handle_content_block_stop(state) + + mock_logger.warning.assert_called_once() + call_args = mock_logger.warning.call_args + assert "test_tool" in str(call_args) + assert "{invalid json}" in str(call_args) + + def test_handle_message_stop(): event: MessageStopEvent = {"stopReason": "end_turn"}