From b579f56ca376efd0e65bf06362949b5f2aca25f7 Mon Sep 17 00:00:00 2001 From: Sameer Kankute Date: Fri, 20 Feb 2026 12:27:41 +0530 Subject: [PATCH] Fix mapping of parallel_tool_calls for bedrock converse --- .../bedrock/chat/converse_transformation.py | 19 ++++++ .../chat/test_converse_transformation.py | 66 ++++++++++++++++++- 2 files changed, 82 insertions(+), 3 deletions(-) diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index daac3e6a00..66a69104d7 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -511,6 +511,7 @@ class AmazonConverseConfig(BaseConfig): "response_format", "requestMetadata", "service_tier", + "parallel_tool_calls", ] if ( @@ -913,6 +914,13 @@ class AmazonConverseConfig(BaseConfig): ) if _tool_choice_value is not None: optional_params["tool_choice"] = _tool_choice_value + if param == "parallel_tool_calls": + disable_parallel = not value + optional_params["_parallel_tool_use_config"] = { + "tool_choice": { + "disable_parallel_tool_use": disable_parallel + } + } if param == "thinking": optional_params["thinking"] = value elif param == "reasoning_effort" and isinstance(value, str): @@ -1202,6 +1210,17 @@ class AmazonConverseConfig(BaseConfig): k: v for k, v in inference_params.items() if k in total_supported_params } + # Handle parallel_tool_calls configuration + parallel_tool_use_config = additional_request_params.pop("_parallel_tool_use_config", None) + if parallel_tool_use_config is not None: + # Merge the tool_choice config from parallel_tool_calls into additional_request_params + for key, value in parallel_tool_use_config.items(): + if key in additional_request_params and isinstance(additional_request_params[key], dict) and isinstance(value, dict): + # Merge dictionaries + additional_request_params[key].update(value) + else: + additional_request_params[key] = value + # Only set the topK value in for models that support it additional_request_params.update( self._handle_top_k_value(model, inference_params) diff --git a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py index c773db2107..2639559716 100644 --- a/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/test_litellm/llms/bedrock/chat/test_converse_transformation.py @@ -2616,11 +2616,11 @@ def test_empty_assistant_message_handling(): empty or whitespace-only content with a placeholder to prevent AWS Bedrock Converse API 400 Bad Request errors. """ + # Import the litellm module that factory.py uses to ensure we patch the correct reference + import litellm.litellm_core_utils.prompt_templates.factory as factory_module from litellm.litellm_core_utils.prompt_templates.factory import ( _bedrock_converse_messages_pt, ) - # Import the litellm module that factory.py uses to ensure we patch the correct reference - import litellm.litellm_core_utils.prompt_templates.factory as factory_module # Test case 1: Empty string content - test with modify_params=True to prevent merging messages = [ @@ -3135,7 +3135,12 @@ def test_native_structured_output_no_fake_stream(): def test_transform_request_with_output_config(): """Test that outputConfig flows through _transform_request_helper into the final request.""" - from litellm.types.llms.bedrock import OutputConfigBlock, OutputFormat, OutputFormatStructure, JsonSchemaDefinition + from litellm.types.llms.bedrock import ( + JsonSchemaDefinition, + OutputConfigBlock, + OutputFormat, + OutputFormatStructure, + ) config = AmazonConverseConfig() @@ -3377,6 +3382,61 @@ def test_output_config_applies_additional_properties(): +def test_parallel_tool_calls_in_request_transformation(): + """Test that parallel_tool_calls is correctly placed in additionalModelRequestFields after full transformation""" + config = AmazonConverseConfig() + + messages = [ + {"role": "user", "content": "What's the weather in SF and NYC?"} + ] + + non_default_params = { + "parallel_tool_calls": False, + "tools": [ + { + "type": "function", + "function": { + "name": "get_weather", + "description": "Get the weather", + "parameters": { + "type": "object", + "properties": { + "location": { + "type": "string", + "description": "The location to get weather for" + } + }, + "required": ["location"] + } + } + } + ], + "max_tokens": 100, + } + + optional_params = config.map_openai_params( + non_default_params=non_default_params, + optional_params={}, + model="anthropic.claude-sonnet-4-5-v2:0", + drop_params=False, + ) + + # Transform the request + request_data = config.transform_request( + model="anthropic.claude-sonnet-4-5-v2:0", + messages=messages, + optional_params=optional_params, + litellm_params={}, + headers={}, + ) + + # Verify the structure + assert "additionalModelRequestFields" in request_data + assert "tool_choice" in request_data["additionalModelRequestFields"] + assert "disable_parallel_tool_use" in request_data["additionalModelRequestFields"]["tool_choice"] + assert request_data["additionalModelRequestFields"]["tool_choice"]["disable_parallel_tool_use"] is True + + class TestBedrockMinThinkingBudgetTokens: """Test that thinking.budget_tokens is clamped to the Bedrock minimum (1024)."""