diff --git a/src/strands/event_loop/streaming.py b/src/strands/event_loop/streaming.py index 0a1161135..f304eda7d 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 as JSON; falling back to empty dict", + current_tool_use.get("name"), + current_tool_use["input"][:200], + ) 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 93f8d95f8..ff82ce76c 100644 --- a/tests/strands/event_loop/test_streaming.py +++ b/tests/strands/event_loop/test_streaming.py @@ -527,6 +527,25 @@ def test_handle_content_block_stop(state, exp_updated_state): assert tru_updated_state == exp_updated_state +def test_handle_content_block_stop_invalid_json_logs_warning(caplog): + state = { + "content": [], + "current_tool_use": {"toolUseId": "123", "name": "my_tool", "input": "not valid json"}, + "text": "", + "reasoningText": "", + "citationsContent": [], + "redactedContent": b"", + } + + import logging + + with caplog.at_level(logging.WARNING, logger="strands.event_loop.streaming"): + tru_updated_state = strands.event_loop.streaming.handle_content_block_stop(state) + + assert tru_updated_state["content"] == [{"toolUse": {"toolUseId": "123", "name": "my_tool", "input": {}}}] + assert any("failed to parse tool input as JSON" in record.message for record in caplog.records) + + def test_handle_message_stop(): event: MessageStopEvent = {"stopReason": "end_turn"}