Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions litellm/litellm_core_utils/prompt_templates/factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5035,15 +5035,18 @@ def _bedrock_tools_pt(tools: List, model: Optional[str] = None) -> List[BedrockT
]
"""
from litellm.llms.bedrock.common_utils import (
get_bedrock_base_model,
bedrock_converse_supports_strict_tools,
normalize_json_schema_custom_types_to_object,
)
from litellm.litellm_core_utils.prompt_templates.common_utils import unpack_defs

_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"))
# (Nova, Llama, GPT-OSS) reject the strict field outright. Opus 4.7/4.8
# also reject `strict` on Bedrock Converse (see #31582) — their validator
# maps toolSpec to the native Anthropic tool shape, which has no strict
# field, even though Anthropic's native API accepts it as a top-level key.
supports_strict_tools = bool(model and bedrock_converse_supports_strict_tools(model))
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)
Expand Down
47 changes: 47 additions & 0 deletions litellm/llms/bedrock/common_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@
Common utilities used across bedrock chat/embedding/image generation
"""

import contextlib
import functools
import json
import os
import re
from typing import (
TYPE_CHECKING,
Any,
Expand Down Expand Up @@ -718,6 +720,51 @@ def is_claude_4_5_on_bedrock(model: str) -> bool:
return any(pattern in model_lower for pattern in claude_4_5_patterns)


_BEDROCK_MODEL_VERSION_SUFFIX_RE = re.compile(r"-v\d+(?::\d+)?$")


def bedrock_converse_supports_strict_tools(model: str) -> bool:
"""
Whether ``toolSpec.strict`` can be forwarded to Bedrock Converse for ``model``.

Non-Anthropic Bedrock families (Nova, Llama, GPT-OSS) reject the field
outright. Anthropic models forward it unless their entry in
``model_prices_and_context_window.json`` sets
``bedrock_converse_supports_strict_tools: false`` — Bedrock routes those
(Opus 4.7/4.8, see #31582) through a stricter validator that rejects the
``strict`` key on ``toolSpec`` even though Anthropic's native API accepts
it as a top-level tool field.
"""
base = get_bedrock_base_model(model)
if not base.startswith("anthropic"):
return False
flag = _get_bedrock_converse_strict_tools_flag(base)
return flag if flag is not None else True


def _get_bedrock_converse_strict_tools_flag(base_model: str) -> Optional[bool]:
candidates = dict.fromkeys((base_model, _BEDROCK_MODEL_VERSION_SUFFIX_RE.sub("", base_model)))
for candidate in candidates:
with contextlib.suppress(Exception):
model_info = get_cached_model_info()(
model=candidate,
custom_llm_provider="bedrock",
)

flag = model_info.get("bedrock_converse_supports_strict_tools")
if isinstance(flag, bool):
return flag

model_cost_key = model_info.get("key")
if isinstance(model_cost_key, str):
local_flag = (
_get_local_model_cost_map().get(model_cost_key, {}).get("bedrock_converse_supports_strict_tools")
)
if isinstance(local_flag, bool):
return local_flag
return None


def normalize_bedrock_opus_output_config_effort(model: str, output_config: Any) -> None:
"""
Normalize Anthropic ``output_config.effort`` values for Bedrock Opus ids.
Expand Down
11 changes: 11 additions & 0 deletions litellm/model_prices_and_context_window_backup.json
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,7 @@
"bedrock_output_config_effort_ceiling": "max"
},
"anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1203,6 +1204,7 @@
"supports_output_config": true
},
"global.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1237,6 +1239,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"us.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1271,6 +1274,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"eu.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1305,6 +1309,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"au.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1471,6 +1476,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1505,6 +1511,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"global.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1539,6 +1546,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"us.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1573,6 +1581,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"eu.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1607,6 +1616,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"au.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1641,6 +1651,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"jp.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"cache_creation_input_token_cost": 6.875e-06,
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
Expand Down
1 change: 1 addition & 0 deletions litellm/types/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
supports_output_config: Optional[bool]
supports_image_size: Optional[bool]
bedrock_output_config_effort_ceiling: Optional[Literal["low", "medium", "high", "max", "xhigh"]]
bedrock_converse_supports_strict_tools: Optional[bool]


class SearchContextCostPerQuery(TypedDict, total=False):
Expand Down
1 change: 1 addition & 0 deletions litellm/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5458,6 +5458,7 @@ def _get_model_info_helper(
supports_xhigh_reasoning_effort=_model_info.get("supports_xhigh_reasoning_effort", None),
supports_max_reasoning_effort=_model_info.get("supports_max_reasoning_effort", None),
bedrock_output_config_effort_ceiling=_model_info.get("bedrock_output_config_effort_ceiling", None),
bedrock_converse_supports_strict_tools=_model_info.get("bedrock_converse_supports_strict_tools", None),
supports_computer_use=_model_info.get("supports_computer_use", None),
search_context_cost_per_query=_model_info.get("search_context_cost_per_query", None),
web_search_billing_unit=_model_info.get("web_search_billing_unit", None),
Expand Down
11 changes: 11 additions & 0 deletions model_prices_and_context_window.json
Original file line number Diff line number Diff line change
Expand Up @@ -1154,6 +1154,7 @@
"bedrock_output_config_effort_ceiling": "max"
},
"anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1203,6 +1204,7 @@
"supports_output_config": true
},
"global.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1237,6 +1239,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"us.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1271,6 +1274,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"eu.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1305,6 +1309,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"au.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1471,6 +1476,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1505,6 +1511,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"global.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.25e-06,
"cache_creation_input_token_cost_above_1hr": 1e-05,
Expand Down Expand Up @@ -1539,6 +1546,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"us.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1573,6 +1581,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"eu.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1607,6 +1616,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"au.anthropic.claude-opus-4-8": {
"bedrock_converse_supports_strict_tools": false,
"supports_adaptive_thinking": true,
"cache_creation_input_token_cost": 6.875e-06,
"cache_creation_input_token_cost_above_1hr": 1.1e-05,
Expand Down Expand Up @@ -1641,6 +1651,7 @@
"bedrock_output_config_effort_ceiling": "xhigh"
},
"jp.anthropic.claude-opus-4-7": {
"bedrock_converse_supports_strict_tools": false,
"cache_creation_input_token_cost": 6.875e-06,
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"""Regression tests for Bedrock Converse ``toolSpec.strict`` forwarding.

Bedrock Converse routes Claude Opus 4.7/4.8 through an Anthropic-compatible
validator that rejects ``toolSpec.strict`` even though Anthropic's native API
accepts ``strict`` as a top-level tool field for the same models. See
BerriAI/litellm#31582.
"""

import pytest

from litellm.litellm_core_utils.prompt_templates.factory import _bedrock_tools_pt
from litellm.llms.bedrock.common_utils import bedrock_converse_supports_strict_tools


_STRICT_TOOL = [
{
"type": "function",
"function": {
"name": "get_weather",
"strict": True,
"description": "Get the weather for a city",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string"},
"unit": {"type": "string", "enum": ["celsius"]},
},
"required": ["city", "unit"],
"additionalProperties": False,
},
},
}
]


@pytest.mark.parametrize(
"model_id",
[
"bedrock/us.anthropic.claude-opus-4-7",
"bedrock/us.anthropic.claude-opus-4-8",
"anthropic.claude-opus-4-7",
"anthropic.claude-opus-4-8",
"anthropic.claude-opus-4-7-v1:0",
"bedrock/eu.anthropic.claude-opus-4-8-v1:0",
"bedrock/global.anthropic.claude-opus-4-7",
],
)
def test_bedrock_tools_pt_strict_dropped_for_opus_47_48(model_id: str) -> None:
"""Opus 4.7/4.8 on Bedrock Converse reject toolSpec.strict — must be dropped."""
result = _bedrock_tools_pt(_STRICT_TOOL, model=model_id)
assert "strict" not in result[0]["toolSpec"], f"strict leaked into toolSpec for {model_id}: {result[0]['toolSpec']}"


@pytest.mark.parametrize(
"model_id",
[
"anthropic.claude-sonnet-4-5-20250929-v1:0",
"bedrock/us.anthropic.claude-sonnet-4-6",
"bedrock/us.anthropic.claude-opus-4-6",
"bedrock/us.anthropic.claude-opus-4-5",
],
)
def test_bedrock_tools_pt_strict_kept_for_other_anthropic(model_id: str) -> None:
"""Sonnet 4.5/4.6 and Opus <=4.6 accept toolSpec.strict — keep forwarding it."""
result = _bedrock_tools_pt(_STRICT_TOOL, model=model_id)
assert result[0]["toolSpec"]["strict"] is True, f"strict missing for {model_id}: {result[0]['toolSpec']}"


@pytest.mark.parametrize(
"model_id",
[
"us.amazon.nova-micro-v1:0",
"meta.llama3-2-11b-instruct-v1:0",
],
)
def test_bedrock_tools_pt_strict_dropped_for_non_anthropic(model_id: str) -> None:
"""Non-Anthropic Bedrock families reject toolSpec.strict — must be dropped."""
result = _bedrock_tools_pt(_STRICT_TOOL, model=model_id)
assert "strict" not in result[0]["toolSpec"]


def test_bedrock_converse_supports_strict_tools_helper() -> None:
"""Direct check for the gate helper used by factory.py."""
assert bedrock_converse_supports_strict_tools("bedrock/us.anthropic.claude-opus-4-7") is False
assert bedrock_converse_supports_strict_tools("bedrock/us.anthropic.claude-opus-4-8") is False
assert bedrock_converse_supports_strict_tools("anthropic.claude-sonnet-4-5-20250929-v1:0") is True
assert bedrock_converse_supports_strict_tools("bedrock/us.anthropic.claude-opus-4-6") is True
assert bedrock_converse_supports_strict_tools("us.amazon.nova-micro-v1:0") is False
assert bedrock_converse_supports_strict_tools("") is False


@pytest.mark.parametrize(
"cost_map_key",
[
"anthropic.claude-opus-4-7",
"us.anthropic.claude-opus-4-7",
"anthropic.claude-opus-4-8",
"us.anthropic.claude-opus-4-8",
],
)
def test_strict_tools_flag_set_in_model_cost_map(cost_map_key: str) -> None:
"""The gate is driven by ``bedrock_converse_supports_strict_tools: false`` in
``model_prices_and_context_window.json``, not hardcoded model patterns."""
from litellm.litellm_core_utils.get_model_cost_map import GetModelCostMap

cost_map = GetModelCostMap.load_local_model_cost_map()
assert cost_map[cost_map_key]["bedrock_converse_supports_strict_tools"] is False
1 change: 1 addition & 0 deletions tests/test_litellm/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -858,6 +858,7 @@ def test_aaamodel_prices_and_context_window_json_is_valid():
"type": "string",
"enum": ["low", "medium", "high", "max", "xhigh"],
},
"bedrock_converse_supports_strict_tools": {"type": "boolean"},
"tpm": {"type": "number"},
"provider_specific_entry": {"type": "object"},
"supported_endpoints": {
Expand Down
Loading