From ba9b718e812343a3dc6ff1e1e425260516d9456d Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:14:30 +0000 Subject: [PATCH 1/2] feat(bedrock): forward strict and additionalProperties to Converse toolSpec Bedrock Converse supports strict in toolSpec since 2026-02, but _bedrock_tools_pt only whitelisted type/properties/required/name/description, so strict: true was silently dropped and Claude-on-Bedrock ignored enum constraints that GPT and direct-Anthropic honored. Forward strict from the OpenAI function and additionalProperties from the schema (Bedrock requires the latter alongside strict), passing each only when present. https://claude.ai/code/session_01WQjWd8NfUB3vxERwudbHkv --- .../prompt_templates/factory.py | 17 ++++--- litellm/types/llms/bedrock.py | 2 + ...llm_core_utils_prompt_templates_factory.py | 46 +++++++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 1460dbaf0a9..219bfa18b54 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -5548,16 +5548,21 @@ def _bedrock_tools_pt( normalize_json_schema_custom_types_to_object(parameters) if parameters.get("type") not in _valid_json_schema_root_types: parameters["type"] = "object" - tool_input_schema = BedrockToolInputSchemaBlock( - json=BedrockToolJsonSchemaBlock( - type=parameters["type"], - properties=parameters.get("properties", {}), - required=parameters.get("required", []), - ) + json_schema = BedrockToolJsonSchemaBlock( + type=parameters["type"], + properties=parameters.get("properties", {}), + required=parameters.get("required", []), ) + additional_properties = parameters.get("additionalProperties", None) + if additional_properties is not None: + json_schema["additionalProperties"] = additional_properties + tool_input_schema = BedrockToolInputSchemaBlock(json=json_schema) tool_spec = BedrockToolSpecBlock( inputSchema=tool_input_schema, name=name, description=description ) + strict = tool.get("function", {}).get("strict", None) + if strict is not None: + tool_spec["strict"] = strict 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 60e640636aa..ed80b44414b 100644 --- a/litellm/types/llms/bedrock.py +++ b/litellm/types/llms/bedrock.py @@ -250,6 +250,7 @@ class ToolJsonSchemaBlock(TypedDict, total=False): type: Literal["object"] properties: dict required: List[str] + additionalProperties: bool class ToolInputSchemaBlock(TypedDict): @@ -260,6 +261,7 @@ class ToolSpecBlock(TypedDict, total=False): inputSchema: Required[ToolInputSchemaBlock] name: Required[str] description: str + strict: bool class SystemToolBlock(TypedDict, total=False): diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index 91fd07dcffc..ed6eec7c3aa 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -898,6 +898,52 @@ def test_bedrock_tools_unpack_defs(): _bedrock_tools_pt(tools=tools) +def test_bedrock_tools_pt_strict_parameter(): + """Regression for strict tools silently dropped on the Bedrock Converse path. + + Bedrock Converse supports strict in toolSpec since 2026-02; without forwarding + strict (and additionalProperties, which Bedrock requires alongside strict) the + model ignores the enum constraint the caller asked for. + """ + 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 + + 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_bedrock_image_processor_content_type_fallback_url_extension(): """ Test that _post_call_image_processing falls back to URL extension From d2233bee6a95a8ef1a29d00209fbda809117b58f Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 5 Jun 2026 21:39:02 +0000 Subject: [PATCH 2/2] fix(bedrock): only forward strict tool schemas to Claude on Converse Nova, Llama and GPT-OSS on Bedrock reject the strict field (BedrockException 'This model doesn't support the strict field'), and the GPT-OSS request-body test asserts strict/additionalProperties are stripped. Forwarding them to every model broke the llm_translation suite, so gate the forwarding on the anthropic base model since only Claude honours strict tool schemas on Bedrock. --- .../prompt_templates/factory.py | 10 +++++++-- ...llm_core_utils_prompt_templates_factory.py | 21 +++++++++++++------ 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/litellm/litellm_core_utils/prompt_templates/factory.py b/litellm/litellm_core_utils/prompt_templates/factory.py index 219bfa18b54..588a183d58e 100644 --- a/litellm/litellm_core_utils/prompt_templates/factory.py +++ b/litellm/litellm_core_utils/prompt_templates/factory.py @@ -5496,6 +5496,7 @@ def _bedrock_tools_pt( ] """ from litellm.llms.bedrock.common_utils import ( + get_bedrock_base_model, normalize_json_schema_custom_types_to_object, ) from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs @@ -5503,6 +5504,11 @@ def _bedrock_tools_pt( _valid_json_schema_root_types = frozenset( ("array", "boolean", "integer", "null", "number", "object", "string") ) + # Only Claude on Bedrock honours strict tool schemas; other families + # (Nova, Llama, GPT-OSS) reject the strict field outright. + supports_strict_tools = bool( + model and get_bedrock_base_model(model).startswith("anthropic") + ) tool_block_list: List[BedrockToolBlock] = [] for tool_idx, tool in enumerate(tools): # Check if tool is already a BedrockToolBlock (e.g., systemTool for Nova grounding) @@ -5554,14 +5560,14 @@ def _bedrock_tools_pt( required=parameters.get("required", []), ) additional_properties = parameters.get("additionalProperties", None) - if additional_properties is not None: + if supports_strict_tools and additional_properties is not None: json_schema["additionalProperties"] = additional_properties tool_input_schema = BedrockToolInputSchemaBlock(json=json_schema) tool_spec = BedrockToolSpecBlock( inputSchema=tool_input_schema, name=name, description=description ) strict = tool.get("function", {}).get("strict", None) - if strict is not None: + if supports_strict_tools and strict is not None: tool_spec["strict"] = strict tool_block = BedrockToolBlock(toolSpec=tool_spec) tool_block_list.append(tool_block) diff --git a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py index ed6eec7c3aa..3bf3b04bf14 100644 --- a/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py +++ b/tests/test_litellm/litellm_core_utils/prompt_templates/test_litellm_core_utils_prompt_templates_factory.py @@ -899,11 +899,12 @@ def test_bedrock_tools_unpack_defs(): def test_bedrock_tools_pt_strict_parameter(): - """Regression for strict tools silently dropped on the Bedrock Converse path. + """Regression for strict tools on the Bedrock Converse path. - Bedrock Converse supports strict in toolSpec since 2026-02; without forwarding - strict (and additionalProperties, which Bedrock requires alongside strict) the - model ignores the enum constraint the caller asked for. + Claude on Bedrock honours strict in toolSpec (with additionalProperties, which + Bedrock requires alongside strict); without forwarding it the model ignores the + enum constraint the caller asked for. Every other Bedrock family (Nova, Llama, + GPT-OSS) rejects the strict field, so it must only be forwarded for Claude. """ tools_with_strict = [ { @@ -921,10 +922,16 @@ def test_bedrock_tools_pt_strict_parameter(): }, } ] - result = _bedrock_tools_pt(tools_with_strict) + result = _bedrock_tools_pt( + tools_with_strict, model="anthropic.claude-sonnet-4-5-20250929-v1:0" + ) assert result[0]["toolSpec"]["strict"] is True assert result[0]["toolSpec"]["inputSchema"]["json"]["additionalProperties"] is False + result = _bedrock_tools_pt(tools_with_strict, model="us.amazon.nova-micro-v1:0") + assert "strict" not in result[0]["toolSpec"] + assert "additionalProperties" not in result[0]["toolSpec"]["inputSchema"]["json"] + tools_without_strict = [ { "type": "function", @@ -939,7 +946,9 @@ def test_bedrock_tools_pt_strict_parameter(): }, } ] - result = _bedrock_tools_pt(tools_without_strict) + result = _bedrock_tools_pt( + tools_without_strict, model="anthropic.claude-sonnet-4-5-20250929-v1:0" + ) assert "strict" not in result[0]["toolSpec"] assert "additionalProperties" not in result[0]["toolSpec"]["inputSchema"]["json"]