Skip to content

fix(bedrock): fold streaming tool input deltas back into tool args (#6149) - #6152

Closed
tsushanth wants to merge 1 commit into
crewAIInc:mainfrom
tsushanth:fix-bedrock-streaming-tool-args
Closed

fix(bedrock): fold streaming tool input deltas back into tool args (#6149)#6152
tsushanth wants to merge 1 commit into
crewAIInc:mainfrom
tsushanth:fix-bedrock-streaming-tool-args

Conversation

@tsushanth

@tsushanth tsushanth commented Jun 13, 2026

Copy link
Copy Markdown

Closes #6149. Streaming twin of #4972 (non-streaming case already fixed by #5415).

_handle_streaming_converse and _ahandle_streaming_converse accumulate Bedrock Converse's contentBlockDelta JSON chunks into a local accumulated_tool_input string but never fold that string back into current_tool_use["input"]. At contentBlockStop the code reads current_tool_use.get("input", {}), which the start event left as the empty dict, so every streaming tool call reaches _handle_tool_execution with {} and pydantic raises Field required [type=missing].

The fix is a focused parse-on-stop, shared between the sync and async paths:

  • new module-level _parse_streaming_tool_input(accumulated, fallback) that returns json.loads(accumulated) when deltas arrived, falls back to the start-block's input dict otherwise (some providers send the full input up front), and degrades to {} on JSONDecodeError so a malformed partial doesn't crash the stream
  • both contentBlockStop branches now call the helper instead of reading current_tool_use["input"] directly

current_tool_use["input"] is intentionally left untouched in the dict that's later re-appended to messages ({"toolUse": current_tool_use}) — the assistant turn echoed back to the model uses Bedrock's own start-block payload, not the parsed args, and changing that shape is out of scope here.

Regression test

lib/crewai/tests/llms/bedrock/test_bedrock.py::test_streaming_tool_call_accumulates_input_deltas:

  • Builds a synthetic Converse stream — contentBlockStart(toolUse) → contentBlockDelta('{"city":') → contentBlockDelta(' "Paris"}') → contentBlockStop → messageStop — exactly the shape the issue describes.
  • Stubs an available_functions["get_weather"] that records its city arg.
  • Asserts the captured arg is {"city": "Paris"}.

Verified locally — fails against main with AssertionError: {} == {'city': 'Paris'} and the WARNING ... Bedrock streaming returned empty content log; passes with the fix.

No async regression test added: the existing test_bedrock_async.py cases are all skipped in CI (VCR doesn't play back aiobotocore), and the parsing logic is shared with the sync path through _parse_streaming_tool_input, so the sync test exercises the relevant code in both call sites.

Summary by CodeRabbit

Release Notes

  • Bug Fixes
    • Fixed tool call argument reconstruction in Bedrock Converse streaming to properly accumulate fragmented inputs into complete function arguments instead of returning incomplete or empty values.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2a639447-bc49-4efc-bc0f-44bec4a1adff

📥 Commits

Reviewing files that changed from the base of the PR and between d80719d and cdc145a.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/llms/providers/bedrock/completion.py
  • lib/crewai/tests/llms/bedrock/test_bedrock.py

📝 Walkthrough

Walkthrough

This PR fixes a streaming bug where Bedrock tool calls receive empty arguments. The fix introduces a helper function to parse accumulated streaming deltas, applies it to both sync and async streaming handlers at content-block termination, and validates the behavior with regression tests.

Changes

Bedrock streaming tool-call argument accumulation

Layer / File(s) Summary
Streaming tool input parsing helper
lib/crewai/src/crewai/llms/providers/bedrock/completion.py
Introduces _parse_streaming_tool_input helper that JSON-parses accumulated delta text with fallback to a provided default dictionary on decode failure or empty accumulation.
Streaming content block stop handlers
lib/crewai/src/crewai/llms/providers/bedrock/completion.py
Both sync and async streaming handlers now invoke _parse_streaming_tool_input(accumulated_tool_input, current_tool_use["input"]) at contentBlockStop instead of reading the empty initial input block directly.
Regression test for accumulated input deltas
lib/crewai/tests/llms/bedrock/test_bedrock.py
Adds _make_streaming_tool_use_events mock-event generator and test_streaming_tool_call_accumulates_input_deltas regression test that verifies fragmented tool-use input chunks are reassembled into final arguments and the tool is invoked with the reconstructed payload.

🐰 A streaming bug we did find,
Empty args left behind,
Now deltas stack with parsed JSON back,
Tool calls work—no empty pack!

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: folding streaming tool input deltas back into tool arguments for Bedrock, matching the changeset's core objective.
Linked Issues check ✅ Passed All coding objectives from #6149 are met: the helper function parses accumulated JSON deltas, both sync/async contentBlockStop handlers use it, and a regression test validates streaming tool-call argument reconstruction.
Out of Scope Changes check ✅ Passed All changes are scoped to the streaming Bedrock tool-call argument fix; no unrelated modifications to other providers, tool-use payload shapes, or unrelated modules were introduced.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@tsushanth

Copy link
Copy Markdown
Author

Apologies @kimnamu — your #6150 was open ~9 hours before this and addresses the same root cause in the same file (bedrock/completion.py). I missed it because the Fixes: keyword didn't auto-link it to issue #6149 in the sidebar. Closing in favor of yours. The streaming-mock test I added here (_make_streaming_tool_use_events helper + test_streaming_tool_call_accumulates_input_deltas) is a different shape from your tests; happy for you to lift it if useful, otherwise feel free to ignore.

@tsushanth tsushanth closed this Jun 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] Bedrock streaming tool calls receive empty arguments ({}) — streaming twin of #4972

1 participant