Skip to content

[BUG] Streaming has_tool_use tracking fails to trigger end_turn → tool_use stop reason override in certain edge cases #1810

@Donyme

Description

@Donyme

Checks

  • I have updated to the lastest minor and patch version of Strands
  • I have checked the documentation and this is not expected behavior
  • I have searched ./issues and there are no duplicates of my issue

Strands Version

1.21.0

Python Version

3.12

Operating System

Amazon-Linux-2

Installation Method

pip

Steps to Reproduce

  1. Observed when calling sub-sops.
  2. Execution from main SOP works fine but when sub-SOP is called, it gets the full SOP but executes initial 2-3 steps and returns end_turn as stop reason with tool_use content.```{ "stop_reason": "end_turn",
    "message": {
    "role": "assistant",
    "content": [
    {
    "text": "\nNow I need to execute the sub-SOP step by step.\nCurrent variables:\n- orderId = "323223"\n- realm = "something"\n- ticketId = "2324"\n\nStep 1: Fetch region from realm_to_region_mapping\nFor something, region = 'ab'\n\n\n\nregion = "ab"\norderId = "3232233"\nrealm = "something"\nticketId = "2324"\n"
    },
    {
    "toolUse": {
    "toolUseId": "tooluse_...",
    "name": "tool1",
    "input": {
    "jsonOutputFilteringConfig": { ... },
    "orderId": "3232233",
    "realm": "something"
    }
    }
    },
    {
    "toolUse": {
    "toolUseId": "tooluse_...",
    "name": "tool2",
    "input": {}
    }
    }
    ]
    }
    }
3. Code only checks contentBlockStart while setting the has_tool_use to true instead of also checking for contentBlockDelta.
4. Sonnet 4.x are a bit unpredicatble in sending stop_reason, that's why the patch exists already bu only for contentBlockStart.

### Expected Behavior

When the Bedrock streaming response contains contentBlockStart events with toolUse AND a messageStop event with stopReason: "end_turn", the SDK should detect the mismatch and override the stop reason to "tool_use", allowing the agent loop to continue executing the tool calls and feeding results back to the model

### Actual Behavior

The BedrockModel._stream() method has logic to override stop_reason from end_turn to tool_use when the response contains tool use blocks. However, this override fails to fire in certain streaming edge cases, causing the agent loop to terminate prematurely even though the model intended to continue with tool execution.

### Additional Context

RCA: 
The has_tool_use flag is initialized to False at the start of each _stream() call and only set to True when a contentBlockStart chunk with a toolUse start event is processed. In this edge case, the contentBlockStart for the tool use block either:

Arrives in a way that the check doesn't match (e.g., the streaming chunk structure differs from what the condition expects), or
The contentBlockStart event for the malformed tool call (empty input {}) is not emitted by Bedrock at all
Additionally, the tracking only checks contentBlockStart but not contentBlockDelta with toolUse deltas, which could serve as a secondary signal.

### Possible Solution

Add redundant tracking via contentBlockDelta as a fallback:


# Track if we see tool use events - check both start and delta
if "contentBlockStart" in chunk and chunk["contentBlockStart"].get("start", {}).get("toolUse"):
    has_tool_use = True
if "contentBlockDelta" in chunk and chunk["contentBlockDelta"].get("delta", {}).get("toolUse"):
    has_tool_use = True]

### Related Issues

_No response_

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions