Skip to content

fix(bedrock): preserve streaming tool call arguments at contentBlockStop - #6150

Merged
Vidit-Ostwal merged 7 commits into
crewAIInc:mainfrom
kimnamu:fix/bedrock-streaming-tool-args
Sep 4, 2026
Merged

fix(bedrock): preserve streaming tool call arguments at contentBlockStop#6150
Vidit-Ostwal merged 7 commits into
crewAIInc:mainfrom
kimnamu:fix/bedrock-streaming-tool-args

Conversation

@kimnamu

@kimnamu kimnamu commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

Thanks to the crewAI maintainers for the native Bedrock provider — this is a small follow-up in the same spirit as #5415.

Fixes #7243 (re-filed from #6149, which the stale bot auto-closed on 2026-07-20 while this PR was pending;
the first-time-contributor check then closed this PR because its linked issue was no longer open)

Problem

With bedrock/... + stream=True, native tool calls reach the tool executor with empty arguments, causing pydantic Field required errors.

Root cause

In _handle_streaming_converse / _ahandle_streaming_converse, tool input is delivered as JSON-string deltas accumulated into a local accumulated_tool_input, but it is never folded back into current_tool_use["input"]. At contentBlockStop, function_args = current_tool_use.get("input", {}) therefore returns {}. The non-streaming path already reads the completed input directly — only the streaming twin was missing this.

Fix

Parse accumulated_tool_input into current_tool_use["input"] (with a {} fallback) at contentBlockStop, in both sync and async handlers. ~3 lines each; fixes both function_args and the message-history append in one place.

Item Before After
Streaming Bedrock tool call args {} (Field required errors) ❌ {"city": "Paris"}
Non-streaming path (#5415) already correct ✅ unchanged
Public API / signatures / return types ✅ unchanged
Streaming chunk events / order ✅ preserved

Tests

Added test_bedrock_streaming_tool_args.py (sync + async, synthetic Converse stream). Verified it catches the bug:

  • Source fix reverted → both fail with assert {} == {'city': 'Paris'}
  • Restored → pass
  • ruff / mypy clean; full bedrock suite 37 passed / 8 skipped (skips are VCR-gated async tests), no regressions.

Relation to other PRs

This is the streaming counterpart of #5415 (which fixed the non-streaming path described in #4972). Open PR #5739 attempts a similar streaming fix but is stalled (no reviews, no tests) and references the non-streaming issue; this PR is scoped to the streaming bug and adds regression coverage.


This contribution was prepared with the help of an AI agent (Claude Code); a human reviewed the change, rationale, and test results before submission.

Streaming Converse handlers accumulate tool input as JSON string deltas in
accumulated_tool_input but never fold it back into current_tool_use["input"],
so function_args reads an empty {} at contentBlockStop. Parse the accumulated
input into the tool-use block (with a {} fallback) in both the sync and async
streaming handlers. This is the streaming counterpart of the non-streaming fix
in crewAIInc#5415 (issue crewAIInc#4972).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Team

Run ID: 02a22841-51eb-4a10-aa48-c4a6e7aee324

📥 Commits

Reviewing files that changed from the base of the PR and between a024115 and 620202e.

📒 Files selected for processing (2)
  • lib/crewai/src/crewai/llms/providers/bedrock/completion.py
  • lib/crewai/tests/llms/bedrock/test_bedrock_streaming_tool_args.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • lib/crewai/src/crewai/llms/providers/bedrock/completion.py
  • lib/crewai/tests/llms/bedrock/test_bedrock_streaming_tool_args.py

Included review availability: Your plan provides up to 10 included reviews per hour; 8 remain after this review.


📝 Walkthrough

Walkthrough

Sync and async Bedrock streaming handlers now parse accumulated JSON-string tool input at contentBlockStop and store the parsed dictionary in the current tool-use block. Tests cover chunked JSON input and non-dictionary JSON input.

Changes

Bedrock Streaming Tool Call Argument Parsing

Layer / File(s) Summary
Parse accumulated JSON in streaming handlers
lib/crewai/src/crewai/llms/providers/bedrock/completion.py
Both streaming handlers parse accumulated_tool_input at contentBlockStop, store the result in current_tool_use["input"], and derive function_args from the parsed dictionary.
Streaming tool argument regression tests
lib/crewai/tests/llms/bedrock/test_bedrock_streaming_tool_args.py
Helpers synthesize Converse stream events and construct a mocked BedrockCompletion. Tests verify sync and async handling of chunked JSON input and {} coercion for non-dictionary JSON input.

Merge Risk: ⚪ Minimal · up to 62020

The change preserves streamed Bedrock tool arguments for sync and async calls while safely coercing non-object JSON input to empty arguments. No concrete unresolved merge risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 16 functions across 2 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary fix: preserving Bedrock streaming tool-call arguments at contentBlockStop.
Description check ✅ Passed The description identifies the related issue, explains the problem and root cause, describes the fix, and reports targeted tests and quality checks. It provides the required verification information d…
Linked Issues check ✅ Passed The changes satisfy issue #7243 by restoring accumulated JSON tool input in both sync and async Bedrock streaming handlers. The tests also cover non-object JSON coercion to an empty argument object.
Out of Scope Changes check ✅ Passed The implementation and regression tests are directly related to the Bedrock streaming tool-argument bug. No unrelated code changes are identified.
  • Fix all pre-merge checks with AI
✨ 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.

@kimnamu

kimnamu commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Note for maintainers: per CONTRIBUTING, this PR was prepared with the help of an AI agent (reviewed by a human before submission). I don't have permission to apply the llm-generated label myself — could a maintainer please add it? Thank you!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lib/crewai/src/crewai/llms/providers/bedrock/completion.py`:
- Around line 1041-1049: The parsed tool input from
json.loads(accumulated_tool_input) can be a non-dict (e.g., list, string), which
will make function_args invalid for fn(**function_args); change the code that
assigns current_tool_use["input"] so that after json.loads it enforces a dict
shape (if the result is not an instance of dict, set current_tool_use["input"] =
{}), and update the subsequent function_args = cast(dict[str, Any],
current_tool_use.get("input", {})) logic accordingly; apply this same guard in
both the synchronous and asynchronous handlers (the sites that set
current_tool_use["input"] from json.loads and then build function_args) so both
paths validate the parsed JSON before use.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2c7ea93c-7855-4356-909f-becfeca420e5

📥 Commits

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

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

Comment thread lib/crewai/src/crewai/llms/providers/bedrock/completion.py
json.loads on the accumulated tool input can return a valid-but-non-object
JSON value (e.g. a string or list), which would fail at fn(**function_args)
with a TypeError. Enforce a dict shape before use in both the sync and async
streaming handlers, and add a regression test for the non-dict case.

Addresses CodeRabbit review feedback on crewAIInc#6150.
@kimnamu

kimnamu commented Jun 13, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the careful review @coderabbitai — good catch. json.loads can return a valid-but-non-object value (e.g. a JSON string or array), which would then fail at fn(**function_args) with a TypeError.

I've applied the suggested guard in both the sync and async handlers (coerce to {} when the parsed value isn't a dict, and also catch TypeError), and added a regression test (test_streaming_non_dict_tool_input_coerced_to_empty_dict) covering the non-object-JSON case. Reverting the guard makes that new test fail; with it, all 3 tests pass. ruff check/format clean.

@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@kimnamu: That's a thorough follow-up — the extra guard (isinstance(parsed, dict) coercion + TypeError catch) closes the edge case cleanly, and the dedicated regression test makes the contract explicit. The fix is solid across all three scenarios now. Thanks for the diligent iteration! 🐇✨

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

This PR is stale because it has been open for 45 days with no activity.

@coderabbitai

coderabbitai Bot commented Sep 3, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@kimnamu

kimnamu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Still relevant on current main. Updated the branch (merged main at 1074788, no conflicts) and re-verified:

  • With main's completion.py swapped in, the PR's tests fail with assert {} == {'city': 'Paris'} for both sync and async streaming; with the fix, 3/3 pass.
  • Bedrock suite: 38 passed / 8 skipped (VCR-gated). ruff check / ruff format --check / mypy clean on the touched file.
  • Workflows never ran on this PR (first-time contributor gating), so CI needs an approve-and-run from a maintainer.
  • [BUG] Bedrock streaming tool calls receive empty arguments ({}) — streaming twin of #4972 #6149 was auto-closed by the stale bot on 2026-07-20 while this was pending; the bug it describes still reproduces.

Happy to rebase or trim scope if preferred.

@github-actions

github-actions Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Thanks for the pull request.

First-time contributors need an associated open issue before we can review a PR.

  1. Open an issue with a template, or pick an existing open one.
  2. Open a new PR (or reopen this one) whose title or body mentions that issue, for example #123.

See the contributing guide.

@kimnamu

kimnamu commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

Heads up on why this closed — it wasn't the stale bot.

ftc-require-issue requires a first-time contributor's PR to reference an open issue. The issue this fixes, #6149, was auto-closed as inactive on 2026-07-20 while this PR sat approved, so the next time the workflow ran on this PR the reference no longer resolved and the PR was closed.

I can't reopen #6149 or this PR myself (no permission on either), so I re-filed the bug as #7243 and pointed the body here at it. The gate passes against that issue now.

If you still want this branch, a reopen from your side should be all it takes — the branch is up to date with main (merged 92eb5f91), and I re-ran the checks today: reverting only completion.py fails the three new tests, with the fix 38 passed / 8 skipped in the Bedrock suite, ruff and mypy clean.

If you'd rather take one of the other PRs for the same bug (#5739 from 2026-05-07, or #6151), that's fine too — I listed all three in #7243 so it's easy to pick one and close the rest.

@Vidit-Ostwal Vidit-Ostwal reopened this Sep 4, 2026
@kimnamu

kimnamu commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

On the red pip-audit check: it isn't from this branch. It flags gitpython 3.1.58 (PYSEC-2026-3785–3788), snowflake-sqlalchemy 1.10.0 (GHSA-8g6f-qw9x-4q6q) and unstructured 0.18.32 (GHSA-4mvj-m6j5-pmf7) in the shared uv.lock, which this PR doesn't touch — uv.lock and the pyproject.toml files here are identical to main, and other PRs' Vulnerability Scan runs today fail on the same six advisories.

#7244 bumps all three and its pip-audit is green; once that (or an equivalent bump) lands, a re-run of the check here should pass. I've left the dependency bump out of this PR to keep it scoped to the streaming fix. Tests, lint and type-check are green on the current head (1074788).

@Vidit-Ostwal

Copy link
Copy Markdown
Contributor

On the red pip-audit check: it isn't from this branch. It flags gitpython 3.1.58 (PYSEC-2026-3785–3788), snowflake-sqlalchemy 1.10.0 (GHSA-8g6f-qw9x-4q6q) and unstructured 0.18.32 (GHSA-4mvj-m6j5-pmf7) in the shared uv.lock, which this PR doesn't touch — uv.lock and the pyproject.toml files here are identical to main, and other PRs' Vulnerability Scan runs today fail on the same six advisories.

#7244 bumps all three and its pip-audit is green; once that (or an equivalent bump) lands, a re-run of the check here should pass. I've left the dependency bump out of this PR to keep it scoped to the streaming fix. Tests, lint and type-check are green on the current head (1074788).

Yup, already working on a PR to resolve that.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@Vidit-Ostwal
Vidit-Ostwal merged commit c00e322 into crewAIInc:main Sep 4, 2026
90 of 158 checks passed
@Vidit-Ostwal

Copy link
Copy Markdown
Contributor

Thanks a lot @kimnamu

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 ({}) — accumulated input is never folded back at contentBlockStop

2 participants