fix(bedrock): enable tool search for Opus 4.8 and gen 5 Claude on Invoke

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
milan 2026-08-19 21:33:20 +00:00
parent 0de60a2ff2
commit caf9c1890e
7 changed files with 135 additions and 41 deletions

View file

@ -22,6 +22,7 @@ from litellm.llms.bedrock.common_utils import (
normalize_custom_field_on_tools,
normalize_tool_input_schema_types_for_bedrock_invoke,
pop_bedrock_invoke_output_config_format,
supports_tool_search_on_bedrock,
)
from litellm.types.llms.anthropic import ANTHROPIC_TOOL_SEARCH_BETA_HEADER
from litellm.types.llms.openai import AllMessageValues
@ -275,7 +276,7 @@ class AmazonAnthropicClaudeConfig(AmazonInvokeConfig, AnthropicConfig):
if tool_search_used and not (programmatic_tool_calling_used or input_examples_used):
beta_set.discard(ANTHROPIC_TOOL_SEARCH_BETA_HEADER)
if "opus-4" in model.lower() or "opus_4" in model.lower():
if supports_tool_search_on_bedrock(model):
beta_set.add("tool-search-tool-2025-10-19")
auto_beta_list: Final = filter_and_transform_beta_headers(

View file

@ -21,6 +21,7 @@ import httpx
import litellm
from litellm import verbose_logger
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
from litellm.llms.base_llm.anthropic_messages.transformation import (
BaseAnthropicMessagesConfig,
)
@ -742,6 +743,61 @@ def is_claude_4_5_on_bedrock(model: str) -> bool:
)
_BEDROCK_TOOL_SEARCH_MODEL_PATTERNS: Final = (
"opus-4.5",
"opus_4.5",
"opus-4-5",
"opus_4_5",
"sonnet-4.5",
"sonnet_4.5",
"sonnet-4-5",
"sonnet_4_5",
"opus-4.6",
"opus_4.6",
"opus-4-6",
"opus_4_6",
"sonnet-4.6",
"sonnet_4.6",
"sonnet-4-6",
"sonnet_4_6",
"opus-4.7",
"opus_4.7",
"opus-4-7",
"opus_4_7",
"opus-4.8",
"opus_4.8",
"opus-4-8",
"opus_4_8",
"opus-5",
"opus_5",
"sonnet-5",
"sonnet_5",
"haiku-4.5",
"haiku_4.5",
"haiku-4-5",
"haiku_4_5",
)
def supports_tool_search_on_bedrock(model: str) -> bool:
"""
Whether Bedrock InvokeModel accepts the ``tool-search-tool-2025-10-19``
beta for ``model``.
The model map's ``supports_tool_search`` flag is authoritative when
``model`` resolves to an entry that sets it; the name patterns cover ids
the map cannot resolve (ARNs, unlisted regional variants).
Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
"""
catalog: Final = AnthropicModelInfo._get_provider_resolved_capability(model, "supports_tool_search", "bedrock")
if catalog is not None:
return catalog
model_lower: Final = model.lower()
return any(pattern in model_lower for pattern in _BEDROCK_TOOL_SEARCH_MODEL_PATTERNS)
_BEDROCK_MODEL_VERSION_SUFFIX_RE: Final = re.compile(r"-v\d+(?::\d+)?$")

View file

@ -37,6 +37,7 @@ from litellm.llms.bedrock.common_utils import (
normalize_custom_field_on_tools,
normalize_tool_input_schema_types_for_bedrock_invoke,
pop_bedrock_invoke_output_config_format,
supports_tool_search_on_bedrock,
)
from litellm.llms.bedrock.request_metadata import (
bedrock_request_metadata_headers,
@ -390,46 +391,7 @@ class AmazonAnthropicClaudeMessagesConfig(
Returns:
True if the model supports tool search on Bedrock
"""
catalog: Final = AnthropicModelInfo._get_provider_resolved_capability(model, "supports_tool_search", "bedrock")
if catalog is not None:
return catalog
model_lower: Final = model.lower()
supported_patterns: Final = [
# Opus 4.5
"opus-4.5",
"opus_4.5",
"opus-4-5",
"opus_4_5",
# Sonnet 4.5
"sonnet-4.5",
"sonnet_4.5",
"sonnet-4-5",
"sonnet_4_5",
# Opus 4.6
"opus-4.6",
"opus_4.6",
"opus-4-6",
"opus_4_6",
# sonnet 4.6
"sonnet-4.6",
"sonnet_4.6",
"sonnet-4-6",
"sonnet_4_6",
# Opus 4.7
"opus-4.7",
"opus_4.7",
"opus-4-7",
"opus_4_7",
# Haiku 4.5
"haiku-4.5",
"haiku_4.5",
"haiku-4-5",
"haiku_4_5",
]
return any(pattern in model_lower for pattern in supported_patterns)
return supports_tool_search_on_bedrock(model)
def _get_tool_search_beta_header_for_bedrock(
self,

View file

@ -1535,6 +1535,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1571,6 +1572,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1607,6 +1609,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1643,6 +1646,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1679,6 +1683,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1715,6 +1720,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1751,6 +1757,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1788,6 +1795,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1825,6 +1833,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1862,6 +1871,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1899,6 +1909,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1936,6 +1947,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2006,6 +2018,7 @@
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2043,6 +2056,7 @@
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2080,6 +2094,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2117,6 +2132,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2154,6 +2170,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2191,6 +2208,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,

View file

@ -1535,6 +1535,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1571,6 +1572,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1607,6 +1609,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1643,6 +1646,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1679,6 +1683,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1715,6 +1720,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1751,6 +1757,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1788,6 +1795,7 @@
"cache_read_input_token_cost": 5e-07,
"input_cost_per_token": 5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1825,6 +1833,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1862,6 +1871,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1899,6 +1909,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -1936,6 +1947,7 @@
"cache_read_input_token_cost": 5.5e-07,
"input_cost_per_token": 5.5e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2006,6 +2018,7 @@
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2043,6 +2056,7 @@
"cache_read_input_token_cost": 2e-07,
"input_cost_per_token": 2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2080,6 +2094,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2117,6 +2132,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2154,6 +2170,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -2191,6 +2208,7 @@
"cache_read_input_token_cost": 2.2e-07,
"input_cost_per_token": 2.2e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,

View file

@ -545,3 +545,33 @@ def test_output_format_removed_from_bedrock_invoke_request():
assert (
"output_format" not in result
), f"output_format should be removed for Bedrock Invoke, got keys: {result.keys()}"
@pytest.mark.parametrize(
"model, expected",
[
pytest.param("us.anthropic.claude-sonnet-5-v99:9", True, id="sonnet_5"),
pytest.param("us.anthropic.claude-opus-5-v99:9", True, id="opus_5"),
pytest.param("us.anthropic.claude-opus-4-8-v99:9", True, id="opus_4_8"),
pytest.param("us.anthropic.claude-haiku-4-5-v99:9", True, id="haiku_4_5"),
pytest.param("us.anthropic.claude-opus-4-1-v99:9", False, id="opus_4_1_unsupported"),
],
)
def test_bedrock_chat_invoke_tool_search_beta_header(model, expected):
"""The chat Invoke path previously hardcoded an ``opus-4`` name check, which
both missed tool-search capable families (Haiku 4.5, Sonnet 5, Opus 5) and
wrongly sent the beta for Opus 4.1, which Anthropic documents as
unsupported. It now shares ``supports_tool_search_on_bedrock`` with the
/v1/messages path."""
config = AmazonAnthropicClaudeConfig()
betas = config._compute_bedrock_invoke_beta_headers(
model=model,
messages=[{"role": "user", "content": "test"}],
optional_params={
"tools": [{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"}],
},
headers={},
)
assert ("tool-search-tool-2025-10-19" in betas) is expected

View file

@ -2714,6 +2714,9 @@ def test_filter_and_transform_beta_headers_passes_context_management_for_bedrock
"us.anthropic.claude-haiku-4-5-20251001-v1:0",
"us.anthropic.claude-sonnet-4-5-20250929-v1:0",
"us.anthropic.claude-opus-4-7",
"us.anthropic.claude-opus-4-8",
"us.anthropic.claude-opus-5",
"us.anthropic.claude-sonnet-5",
],
)
def test_bedrock_messages_tool_search_adds_beta_header(local_beta_headers_config, model):
@ -2726,6 +2729,9 @@ def test_bedrock_messages_tool_search_adds_beta_header(local_beta_headers_config
Opus 4.7, so the beta was silently dropped for those models and every
tool-search request failed. Verified live 2026-08-11: Bedrock returns 200
with ``server_tool_use`` for all three models once the beta is sent.
Opus 4.8, Opus 5, and Sonnet 5 verified live 2026-08-19: Bedrock
InvokeModel returns 200 with ``server_tool_use`` and
``tool_search_tool_result`` blocks.
"""
from litellm.types.router import GenericLiteLLMParams
@ -2782,6 +2788,9 @@ def test_bedrock_messages_tool_search_model_map_flag_is_authoritative(local_mode
"model, expected",
[
pytest.param("us.anthropic.claude-opus-4-6-v99:9", True, id="unmapped_id_falls_back_to_patterns"),
pytest.param("us.anthropic.claude-opus-5-v99:9", True, id="unmapped_opus_5_falls_back_to_patterns"),
pytest.param("us.anthropic.claude-sonnet-5-v99:9", True, id="unmapped_sonnet_5_falls_back_to_patterns"),
pytest.param("us.anthropic.claude-opus-4-8-v99:9", True, id="unmapped_opus_4_8_falls_back_to_patterns"),
pytest.param("anthropic.claude-3-5-sonnet-20240620-v1:0", False, id="mapped_entry_without_flag_no_pattern"),
],
)