From c51fb46d8529d9ccc4f4efbfcbd95cb7bb916485 Mon Sep 17 00:00:00 2001 From: Ryze0323 Date: Wed, 8 Apr 2026 12:51:43 +0900 Subject: [PATCH] feat(bedrock): pass strict and additionalProperties to Converse API toolSpec Bedrock Converse API supports strict: true in toolSpec since 2026-02-04 (GA). Previously, both strict and additionalProperties were implicitly dropped during OpenAI-to-Bedrock tool conversion because ToolSpecBlock and ToolJsonSchemaBlock only whitelisted a subset of fields. This change: - Adds strict to ToolSpecBlock and passes it through when present - Adds additionalProperties to ToolJsonSchemaBlock and passes it through when present (required by Bedrock when strict: true is set) Without additionalProperties: false, Bedrock rejects strict: true requests with: "For 'object' type, 'additionalProperties' must be explicitly set to false" Fixes the gap where Anthropic direct API path already supports strict (PR #16725) but Bedrock Converse path does not. --- .../prompt_templates/factory.py | 12 ++++- litellm/types/llms/bedrock.py | 6 ++- .../test_bedrock_completion.py | 46 +++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index d29ca1649ff..c5ef53c5ecc 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -5174,15 +5174,25 @@ def _bedrock_tools_pt(tools: List) -> List[BedrockToolBlock]: # with circular references (see issue #19098). unpack_defs handles nested # refs recursively and correctly detects/skips circular references. unpack_defs(parameters, defs_copy) + _additional_properties = parameters.get("additionalProperties", None) tool_input_schema = BedrockToolInputSchemaBlock( json=BedrockToolJsonSchemaBlock( type=parameters.get("type", ""), properties=parameters.get("properties", {}), required=parameters.get("required", []), + **( + {"additionalProperties": _additional_properties} + if _additional_properties is not None + else {} + ), ) ) + _strict = tool.get("function", {}).get("strict", None) tool_spec = BedrockToolSpecBlock( - inputSchema=tool_input_schema, name=name, description=description + inputSchema=tool_input_schema, + name=name, + description=description, + **({"strict": _strict} if _strict is not None else {}), ) tool_block = BedrockToolBlock(toolSpec=tool_spec) tool_block_list.append(tool_block) diff --git a/litellm/types/llms/bedrock.py b/litellm/types/llms/bedrock.py index 54237dfb37a..cb152484188 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -203,13 +203,16 @@ class ConverseResponseBlock(TypedDict, total=False): str ] # end_turn | tool_use | max_tokens | stop_sequence | content_filtered usage: Required[ConverseTokenUsageBlock] - serviceTier: ServiceTierBlock # Optional - only present when serviceTier was sent in request + serviceTier: ( + ServiceTierBlock # Optional - only present when serviceTier was sent in request + ) class ToolJsonSchemaBlock(TypedDict, total=False): type: Literal["object"] properties: dict required: List[str] + additionalProperties: bool class ToolInputSchemaBlock(TypedDict): @@ -220,6 +223,7 @@ class ToolSpecBlock(TypedDict, total=False): inputSchema: Required[ToolInputSchemaBlock] name: Required[str] description: str + strict: Optional[bool] class SystemToolBlock(TypedDict, total=False): diff --git a/tests/llm_translation/test_bedrock_completion.py b/tests/llm_translation/test_bedrock_completion.py index 76ec2bdd1d7..b8de820d364 100644 --- a/tests/llm_translation/test_bedrock_completion.py +++ b/tests/llm_translation/test_bedrock_completion.py @@ -1116,6 +1116,52 @@ def test_bedrock_tools_transformation_valid_params(): assert "test" in result[0]["toolSpec"]["inputSchema"]["json"]["required"] +def test_bedrock_tools_pt_strict_parameter(): + """Test that strict and additionalProperties are passed through to Bedrock toolSpec.""" + tools_with_strict = [ + { + "type": "function", + "function": { + "name": "generate_sql", + "strict": True, + "description": "Generate a SQL query", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + }, + "required": ["query"], + "additionalProperties": False, + }, + }, + } + ] + result = _bedrock_tools_pt(tools_with_strict) + assert result[0]["toolSpec"]["strict"] is True + assert result[0]["toolSpec"]["inputSchema"]["json"]["additionalProperties"] is False + + # Test without strict - should not have strict key or additionalProperties + tools_without_strict = [ + { + "type": "function", + "function": { + "name": "generate_sql", + "description": "Generate a SQL query", + "parameters": { + "type": "object", + "properties": { + "query": {"type": "string"}, + }, + "required": ["query"], + }, + }, + } + ] + result = _bedrock_tools_pt(tools_without_strict) + assert "strict" not in result[0]["toolSpec"] + assert "additionalProperties" not in result[0]["toolSpec"]["inputSchema"]["json"] + + def test_not_found_error(): with pytest.raises(litellm.NotFoundError): completion(