feat(bedrock): forward strict and additionalProperties to Converse toolSpec (#29814)
* 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 * 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.
This commit is contained in:
parent
273855b4e2
commit
d61f7747c0
@ -5496,6 +5496,7 @@ def _bedrock_tools_pt(
|
|||||||
]
|
]
|
||||||
"""
|
"""
|
||||||
from litellm.llms.bedrock.common_utils import (
|
from litellm.llms.bedrock.common_utils import (
|
||||||
|
get_bedrock_base_model,
|
||||||
normalize_json_schema_custom_types_to_object,
|
normalize_json_schema_custom_types_to_object,
|
||||||
)
|
)
|
||||||
from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs
|
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(
|
_valid_json_schema_root_types = frozenset(
|
||||||
("array", "boolean", "integer", "null", "number", "object", "string")
|
("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] = []
|
tool_block_list: List[BedrockToolBlock] = []
|
||||||
for tool_idx, tool in enumerate(tools):
|
for tool_idx, tool in enumerate(tools):
|
||||||
# Check if tool is already a BedrockToolBlock (e.g., systemTool for Nova grounding)
|
# Check if tool is already a BedrockToolBlock (e.g., systemTool for Nova grounding)
|
||||||
@ -5548,16 +5554,21 @@ def _bedrock_tools_pt(
|
|||||||
normalize_json_schema_custom_types_to_object(parameters)
|
normalize_json_schema_custom_types_to_object(parameters)
|
||||||
if parameters.get("type") not in _valid_json_schema_root_types:
|
if parameters.get("type") not in _valid_json_schema_root_types:
|
||||||
parameters["type"] = "object"
|
parameters["type"] = "object"
|
||||||
tool_input_schema = BedrockToolInputSchemaBlock(
|
json_schema = BedrockToolJsonSchemaBlock(
|
||||||
json=BedrockToolJsonSchemaBlock(
|
type=parameters["type"],
|
||||||
type=parameters["type"],
|
properties=parameters.get("properties", {}),
|
||||||
properties=parameters.get("properties", {}),
|
required=parameters.get("required", []),
|
||||||
required=parameters.get("required", []),
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
additional_properties = parameters.get("additionalProperties", 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(
|
tool_spec = BedrockToolSpecBlock(
|
||||||
inputSchema=tool_input_schema, name=name, description=description
|
inputSchema=tool_input_schema, name=name, description=description
|
||||||
)
|
)
|
||||||
|
strict = tool.get("function", {}).get("strict", None)
|
||||||
|
if supports_strict_tools and strict is not None:
|
||||||
|
tool_spec["strict"] = strict
|
||||||
tool_block = BedrockToolBlock(toolSpec=tool_spec)
|
tool_block = BedrockToolBlock(toolSpec=tool_spec)
|
||||||
tool_block_list.append(tool_block)
|
tool_block_list.append(tool_block)
|
||||||
|
|
||||||
|
|||||||
@ -250,6 +250,7 @@ class ToolJsonSchemaBlock(TypedDict, total=False):
|
|||||||
type: Literal["object"]
|
type: Literal["object"]
|
||||||
properties: dict
|
properties: dict
|
||||||
required: List[str]
|
required: List[str]
|
||||||
|
additionalProperties: bool
|
||||||
|
|
||||||
|
|
||||||
class ToolInputSchemaBlock(TypedDict):
|
class ToolInputSchemaBlock(TypedDict):
|
||||||
@ -260,6 +261,7 @@ class ToolSpecBlock(TypedDict, total=False):
|
|||||||
inputSchema: Required[ToolInputSchemaBlock]
|
inputSchema: Required[ToolInputSchemaBlock]
|
||||||
name: Required[str]
|
name: Required[str]
|
||||||
description: str
|
description: str
|
||||||
|
strict: bool
|
||||||
|
|
||||||
|
|
||||||
class SystemToolBlock(TypedDict, total=False):
|
class SystemToolBlock(TypedDict, total=False):
|
||||||
|
|||||||
@ -898,6 +898,61 @@ def test_bedrock_tools_unpack_defs():
|
|||||||
_bedrock_tools_pt(tools=tools)
|
_bedrock_tools_pt(tools=tools)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bedrock_tools_pt_strict_parameter():
|
||||||
|
"""Regression for strict tools on the Bedrock Converse path.
|
||||||
|
|
||||||
|
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 = [
|
||||||
|
{
|
||||||
|
"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, 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",
|
||||||
|
"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, 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"]
|
||||||
|
|
||||||
|
|
||||||
def test_bedrock_image_processor_content_type_fallback_url_extension():
|
def test_bedrock_image_processor_content_type_fallback_url_extension():
|
||||||
"""
|
"""
|
||||||
Test that _post_call_image_processing falls back to URL extension
|
Test that _post_call_image_processing falls back to URL extension
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user