From 3a8eb84761d803eebee160efbd7a885d11d5aa7a Mon Sep 17 00:00:00 2001 From: Rolly Calma <115199279+Ghraven@users.noreply.github.com> Date: Thu, 7 May 2026 10:26:48 +0800 Subject: [PATCH 1/2] fix(bedrock): JSON-parse accumulated_tool_input string in streaming handler MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #4972 When using Bedrock Converse API in streaming mode, tool input arrives as JSON string deltas that get concatenated into `accumulated_tool_input`. At content block stop, this accumulated string is stored directly in `current_tool_use["input"]` and then passed to `_execute_single_native_tool_call` as `function_args` — but it's still a raw JSON string, not a dict. Tools that use Pydantic validation then receive a string instead of the expected dict and fail immediately with `Field required [type=missing, input_value={}, input_type=dict]` because the string can't be validated as a model schema. Fix: at content-block-stop in both sync and async streaming handlers, check if `current_tool_use.get('input')` is a string and JSON-parse it before passing to the executor. Falls back to empty dict on parse failure. From b60fa329a9cd8b21abda32a49740f5e69661cfae Mon Sep 17 00:00:00 2001 From: Rolly Calma <115199279+Ghraven@users.noreply.github.com> Date: Thu, 7 May 2026 10:27:52 +0800 Subject: [PATCH 2/2] fix(bedrock): JSON-parse accumulated_tool_input at contentBlockStop in streaming MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes #4972 During streaming, Bedrock sends tool input as incremental string deltas that get concatenated into `accumulated_tool_input`. When `contentBlockStop` fires, the code reads `current_tool_use.get('input', {})` — but `input` was never updated during streaming; it still holds the initial empty string from `contentBlockStart`. The accumulated JSON string is only in `accumulated_tool_input`, not in `current_tool_use['input']`. This causes every streaming tool call to receive `{}` as arguments, resulting in Pydantic validation failures on tools with required fields. Fix: at `contentBlockStop`, if `accumulated_tool_input` is non-empty, parse it as JSON and write it back to `current_tool_use['input']` before reading `function_args`. Both sync and async streaming handlers are fixed. --- .../crewai/llms/providers/bedrock/completion.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/crewai/src/crewai/llms/providers/bedrock/completion.py b/lib/crewai/src/crewai/llms/providers/bedrock/completion.py index cd323eac00..2c7912b1da 100644 --- a/lib/crewai/src/crewai/llms/providers/bedrock/completion.py +++ b/lib/crewai/src/crewai/llms/providers/bedrock/completion.py @@ -1054,6 +1054,13 @@ def _handle_streaming_converse( logging.debug("Content block stopped in stream") if current_tool_use: function_name = current_tool_use["name"] + # accumulated_tool_input holds the full JSON string for streaming tool calls. + # Parse it back to a dict; fall back to empty dict on failure. + if accumulated_tool_input: + try: + current_tool_use["input"] = json.loads(accumulated_tool_input) + except (json.JSONDecodeError, ValueError): + current_tool_use["input"] = {} function_args = cast( dict[str, Any], current_tool_use.get("input", {}) ) @@ -1636,6 +1643,13 @@ async def _ahandle_streaming_converse( logging.debug("Content block stopped in stream") if current_tool_use: function_name = current_tool_use["name"] + # accumulated_tool_input holds the full JSON string for streaming tool calls. + # Parse it back to a dict; fall back to empty dict on failure. + if accumulated_tool_input: + try: + current_tool_use["input"] = json.loads(accumulated_tool_input) + except (json.JSONDecodeError, ValueError): + current_tool_use["input"] = {} function_args = cast( dict[str, Any], current_tool_use.get("input", {}) )