From fada84a66aaeb7650e4cebfb5d7387f924f51697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=AF=E5=9F=BA=E9=AD=81?= <1412414664@qq.com> Date: Sun, 28 Jun 2026 04:47:57 +0800 Subject: [PATCH] fix bedrock schema bounds filtering --- litellm/llms/bedrock/common_utils.py | 9 ++++- .../test_anthropic_claude3_transformation.py | 37 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/litellm/llms/bedrock/common_utils.py b/litellm/llms/bedrock/common_utils.py index 467e1050c99..f60c969b4bb 100644 --- a/litellm/llms/bedrock/common_utils.py +++ b/litellm/llms/bedrock/common_utils.py @@ -168,10 +168,12 @@ def remove_custom_field_from_tools(request_body: dict) -> None: def normalize_json_schema_custom_types_to_object(schema: dict) -> None: """ - In-place: replace JSON Schema ``type: \"custom\"`` with ``\"object\"`` (iterative walk). + In-place: normalize JSON Schema fields unsupported by Bedrock (iterative walk). Anthropic / Claude Code use ``custom`` for tool schemas; Bedrock Invoke and Bedrock Converse only accept standard JSON Schema type strings. + Bedrock Invoke also rejects ``minimum`` and ``maximum`` on number schemas, + even though other providers accept those JSON Schema validation keywords. Uses an explicit stack (not recursion) to satisfy recursive-function guards in CI. """ @@ -187,6 +189,8 @@ def normalize_json_schema_custom_types_to_object(schema: dict) -> None: seen.add(node_id) if node.get("type") == "custom": node["type"] = "object" + node.pop("minimum", None) + node.pop("maximum", None) items = node.get("items") if isinstance(items, dict): stack.append(items) @@ -211,8 +215,9 @@ def normalize_tool_input_schema_types_for_bedrock_invoke(request_body: dict) -> Bedrock Invoke (Anthropic Messages) validates ``input_schema`` as JSON Schema. Anthropic's API allows ``type: \"custom\"`` for Claude Code custom tools; Bedrock rejects it with: ``tools.0.custom.input_schema.type: Input should be 'object'``. + Bedrock also rejects ``minimum`` and ``maximum`` constraints on number schemas. - Normalizes ``type: \"custom\"`` to ``\"object\"`` throughout each tool's + Normalizes provider-unsupported schema fields throughout each tool's ``input_schema`` (recursive for nested properties, items, combinators). Args: diff --git a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py index c3191f5b5cb..85396280906 100644 --- a/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py +++ b/tests/test_litellm/llms/bedrock/messages/invoke_transformations/test_anthropic_claude3_transformation.py @@ -1,4 +1,3 @@ -import asyncio import copy import json import os @@ -327,6 +326,7 @@ def test_normalize_tool_input_schema_types_for_bedrock_invoke(): """ Claude Code sends ``input_schema.type: \"custom\"`` for custom tools. Bedrock Invoke rejects this; it requires JSON Schema ``type: \"object\"``. + Bedrock also rejects ``minimum`` and ``maximum`` numeric bounds. """ request = { @@ -341,7 +341,31 @@ def test_normalize_tool_input_schema_types_for_bedrock_invoke(): "properties": { "nested": { "type": "custom", - "properties": {"x": {"type": "string"}}, + "properties": { + "x": {"type": "string"}, + "score": { + "type": "number", + "minimum": 0, + "maximum": 1, + }, + "values": { + "type": "array", + "items": { + "type": "number", + "minimum": -1, + "maximum": 1, + }, + }, + "choice": { + "oneOf": [ + { + "type": "number", + "minimum": 0, + "maximum": 10, + } + ] + }, + }, } }, "required": ["nested"], @@ -359,7 +383,14 @@ def test_normalize_tool_input_schema_types_for_bedrock_invoke(): agent_tool = request["tools"][0] assert agent_tool["type"] == "custom" assert agent_tool["input_schema"]["type"] == "object" - assert agent_tool["input_schema"]["properties"]["nested"]["type"] == "object" + nested_schema = agent_tool["input_schema"]["properties"]["nested"] + assert nested_schema["type"] == "object" + assert "minimum" not in nested_schema["properties"]["score"] + assert "maximum" not in nested_schema["properties"]["score"] + assert "minimum" not in nested_schema["properties"]["values"]["items"] + assert "maximum" not in nested_schema["properties"]["values"]["items"] + assert "minimum" not in nested_schema["properties"]["choice"]["oneOf"][0] + assert "maximum" not in nested_schema["properties"]["choice"]["oneOf"][0] assert request["tools"][1]["input_schema"]["type"] == "object" request2 = {"messages": []}