Merge pull request #41702 from BerriAI/litellm_bedrock_invoke_tool_search_opus48_gen5

fix(bedrock): gate Invoke tool search on the model map for Opus 4.8 and gen 5 Claude
This commit is contained in:
Mateo Wang 2026-09-17 17:39:34 -07:00 • committed by GitHub
commit 2fb502a556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 248 additions and 67 deletions

View file

@ -18,6 +18,7 @@ from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation
)
from litellm.llms.bedrock.common_utils import (
apply_bedrock_invoke_structured_output,
bedrock_supports_tool_search,
get_anthropic_beta_from_headers,
normalize_bedrock_opus_output_config_effort,
normalize_custom_field_on_tools,
@ -265,7 +266,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 bedrock_supports_tool_search(model):
beta_set.add("tool-search-tool-2025-10-19")
auto_beta_list: Final = filter_and_transform_beta_headers(

View file

@ -902,6 +902,20 @@ def bedrock_model_accepts_cache_points(model: str | None) -> bool:
return any(entry.get("supports_prompt_caching") is True for entry in entries)
def bedrock_supports_tool_search(model: str) -> bool:
"""
Whether Bedrock InvokeModel admits the ``tool_search_tool_*`` tool types on ``model``.
Backed by the ``supports_tool_search`` flag in ``model_prices_and_context_window.json``,
an exact entry or the ``claude-tool-search`` fallback rule for Claude 4.5 and newer, so a
newly released Claude carries the flag with no code change. An explicit ``false`` on the
resolved entry wins over the rule.
"""
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
return AnthropicModelInfo._supports_model_capability(model, "supports_tool_search", "bedrock")
def is_claude_4_5_on_bedrock(model: str) -> bool:
"""
Check if the model supports Bedrock prompt caching with an extended '1h' TTL

View file

@ -31,6 +31,7 @@ from litellm.llms.bedrock.chat.invoke_transformations.base_invoke_transformation
from litellm.llms.bedrock.common_utils import (
BedrockError,
apply_bedrock_invoke_structured_output,
bedrock_supports_tool_search,
ensure_bedrock_anthropic_messages_tool_names,
get_anthropic_beta_from_headers,
is_claude_4_5_on_bedrock,
@ -386,9 +387,10 @@ class AmazonAnthropicClaudeMessagesConfig(
"""
Check if the model supports tool search on Bedrock.
The model map's ``supports_tool_search`` flag is authoritative when
``model`` resolves to an entry that sets it; the name patterns below
cover ids the map cannot resolve (ARNs, unlisted regional variants).
The model map's ``supports_tool_search`` flag is authoritative: an exact
entry, or the ``claude-tool-search`` fallback rule (Claude 4.5 and newer)
for ids the map cannot resolve (ARNs, unlisted regional variants) and for
mapped entries that carry no opinion.
Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool
@ -398,46 +400,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 bedrock_supports_tool_search(model)
def _get_tool_search_beta_header_for_bedrock(
self,
@ -453,7 +416,8 @@ class AmazonAnthropicClaudeMessagesConfig(
Bedrock requires a different beta header for tool search than the
Anthropic API when tool search is used without programmatic tool
calling or input examples: `tool-search-tool-2025-10-19`, and only on
the models listed in `_supports_tool_search_on_bedrock`.
the models the model map flags as `supports_tool_search`
(`_supports_tool_search_on_bedrock`).
Ref: https://platform.claude.com/docs/en/agents-and-tools/tool-use/tool-search-tool

View file

@ -1810,6 +1810,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,
@ -1847,6 +1848,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,
@ -1884,6 +1886,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,
@ -1921,6 +1924,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,
@ -1957,6 +1961,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,
@ -1993,6 +1998,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,
@ -2029,6 +2035,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,
@ -2067,6 +2074,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,
@ -2105,6 +2113,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,
@ -2143,6 +2152,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,
@ -2180,6 +2190,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,
@ -2217,6 +2228,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,
@ -2287,6 +2299,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,
@ -2325,6 +2338,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,
@ -2363,6 +2377,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,
@ -2401,6 +2416,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,
@ -2438,6 +2454,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,
@ -2475,6 +2492,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,
@ -46130,6 +46148,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -46163,6 +46182,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -46195,6 +46215,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -59505,6 +59526,15 @@
"supports_mid_conversation_system": true
}
},
{
"name": "claude-tool-search",
"pattern": "claude-[a-z]+-(?:4[-._](?:[5-9]|[1-9]\\d)(?!\\d)|[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",
"fill_missing_for_providers": ["anthropic", "bedrock", "bedrock_converse", "vertex_ai-anthropic_models"],
"description": "Claude at version 4.5 or higher, in any id shape that contains claude-<family>-: minors 4.5 through 4.99, any later major-minor, and bare 5+ majors so a new family like claude-fable-5 matches. Two-digit majors are deliberately not matched so ids like claude-opus-41 (4.1) are not read as major 41. Anthropic's tool search docs list every Claude 4.5 and newer model as supported and Opus 4.1 and earlier as unsupported, so the flag follows the version instead of a per-model list. azure_ai is left out on purpose: Anthropic documents tool search as unavailable on Azure-hosted Foundry deployments, and the azure_ai/ key cannot tell those from Anthropic-hosted ones.",
"model_info": {
"supports_tool_search": true
}
},
{
"name": "wandb-reasoning-baseline",
"pattern": "^wandb/",
@ -63253,6 +63283,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63285,6 +63316,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63316,6 +63348,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63456,6 +63489,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63488,6 +63522,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63519,6 +63554,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,

View file

@ -1810,6 +1810,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,
@ -1847,6 +1848,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,
@ -1884,6 +1886,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,
@ -1921,6 +1924,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,
@ -1957,6 +1961,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,
@ -1993,6 +1998,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,
@ -2029,6 +2035,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,
@ -2067,6 +2074,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,
@ -2105,6 +2113,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,
@ -2143,6 +2152,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,
@ -2180,6 +2190,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,
@ -2217,6 +2228,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,
@ -2287,6 +2299,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,
@ -2325,6 +2338,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,
@ -2363,6 +2377,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,
@ -2401,6 +2416,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,
@ -2438,6 +2454,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,
@ -2475,6 +2492,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,
@ -46130,6 +46148,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -46163,6 +46182,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -46195,6 +46215,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock_converse",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -59505,6 +59526,15 @@
"supports_mid_conversation_system": true
}
},
{
"name": "claude-tool-search",
"pattern": "claude-[a-z]+-(?:4[-._](?:[5-9]|[1-9]\\d)(?!\\d)|[5-9](?!\\d)(?:[-._]\\d{1,2}(?!\\d))?)",
"fill_missing_for_providers": ["anthropic", "bedrock", "bedrock_converse", "vertex_ai-anthropic_models"],
"description": "Claude at version 4.5 or higher, in any id shape that contains claude-<family>-: minors 4.5 through 4.99, any later major-minor, and bare 5+ majors so a new family like claude-fable-5 matches. Two-digit majors are deliberately not matched so ids like claude-opus-41 (4.1) are not read as major 41. Anthropic's tool search docs list every Claude 4.5 and newer model as supported and Opus 4.1 and earlier as unsupported, so the flag follows the version instead of a per-model list. azure_ai is left out on purpose: Anthropic documents tool search as unavailable on Azure-hosted Foundry deployments, and the azure_ai/ key cannot tell those from Anthropic-hosted ones.",
"model_info": {
"supports_tool_search": true
}
},
{
"name": "wandb-reasoning-baseline",
"pattern": "^wandb/",
@ -63253,6 +63283,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63285,6 +63316,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63316,6 +63348,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63456,6 +63489,7 @@
"cache_read_input_token_cost": 2.4e-07,
"input_cost_per_token": 2.4e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63488,6 +63522,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,
@ -63519,6 +63554,7 @@
"cache_read_input_token_cost": 6e-07,
"input_cost_per_token": 6e-06,
"litellm_provider": "bedrock",
"supports_tool_search": true,
"max_input_tokens": 1000000,
"max_output_tokens": 128000,
"max_tokens": 128000,

View file

@ -206,6 +206,21 @@ def local_model_cost_map(monkeypatch):
litellm.get_model_info.cache_clear()
@pytest.fixture
def local_beta_headers_config(monkeypatch):
"""Pin the bundled ``anthropic_beta_headers_config.json`` so beta header assertions
do not depend on the network-fetched copy or on what earlier tests left cached."""
from litellm.anthropic_beta_headers_manager import reload_beta_headers_config
monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True")
reload_beta_headers_config()
try:
yield
finally:
monkeypatch.delenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", raising=False)
reload_beta_headers_config()
def _run_coroutine_if_needed(result):
if not asyncio.iscoroutine(result):
return

View file

@ -997,5 +997,54 @@ def test_shipped_claude_thinking_rules_backfill_only_anthropic(shipped_cost_map)
assert match_fill_missing_generalizations("claude-sonnet-4-6", "anthropic") == {
"supports_adaptive_thinking": True,
"supports_legacy_thinking": True,
"supports_tool_search": True,
}
assert match_fill_missing_generalizations("claude-sonnet-4-6", "perplexity") is None
@pytest.mark.parametrize(
"model,provider,tool_search",
[
("us.anthropic.claude-opus-4-5", "bedrock", True),
("claude-haiku-4-4", "anthropic", None),
("claude-haiku-4-6", "anthropic", True),
("claude-opus-4.5", "anthropic", True),
("claude-opus-4_5", "anthropic", True),
("claude-haiku-4-10", "anthropic", True),
("claude-haiku-5-0", "anthropic", True),
("claude-sonnet-5-1", "anthropic", True),
("claude-newfam-6", "anthropic", True),
("claude-haiku-4-20250514", "anthropic", None),
],
)
def test_shipped_tool_search_rule_version_boundaries(shipped_cost_map, model, provider, tool_search):
"""The claude-tool-search rule flags Claude 4.5 and newer in any family, bare major
or major-minor with a dash, dot or underscore delimiter, and leaves 4.4 and
date-suffixed 4.x ids without an opinion."""
assert model not in litellm.model_cost
info = litellm.get_model_info(model, custom_llm_provider=provider)
assert info.get("supports_tool_search") is tool_search, model
def test_shipped_tool_search_rule_fills_mapped_claude_entries_without_flag(shipped_cost_map):
"""A mapped Claude 4.5+ entry with no supports_tool_search key gets it from the rule
on Anthropic direct, Vertex and Bedrock, a mapped pre-4.5 entry stays without one,
and Azure Foundry and reseller copies of the same model are not touched."""
for key, model, provider in (
("claude-opus-4-7", "claude-opus-4-7", "anthropic"),
("vertex_ai/claude-opus-5", "claude-opus-5", "vertex_ai"),
):
assert "supports_tool_search" not in litellm.model_cost[key]
assert litellm.get_model_info(model, custom_llm_provider=provider)["supports_tool_search"] is True
assert "supports_tool_search" not in litellm.model_cost["claude-opus-4-1"]
opus_4_1_info = litellm.get_model_info("claude-opus-4-1", custom_llm_provider="anthropic")
assert opus_4_1_info.get("supports_tool_search") is None
assert "supports_tool_search" not in litellm.model_cost["azure_ai/claude-opus-5"]
azure_opus_5_info = litellm.get_model_info("claude-opus-5", custom_llm_provider="azure_ai")
assert azure_opus_5_info.get("supports_tool_search") is None
assert match_fill_missing_generalizations("claude-opus-5", "bedrock")["supports_tool_search"] is True
assert "supports_tool_search" not in match_fill_missing_generalizations("claude-opus-5", "azure_ai")
assert match_fill_missing_generalizations("claude-opus-5", "perplexity") is None

View file

@ -814,3 +814,48 @@ async def test_bedrock_invoke_claude_async_completion_inlines_document_url_sourc
"type": "document",
"source": {"type": "base64", "media_type": "application/pdf", "data": async_only_image_fetch.base64_png},
} in captured["body"]["messages"][0]["content"]
@pytest.mark.parametrize(
"model, expected_betas",
[
pytest.param("us.anthropic.claude-opus-4-8", ["tool-search-tool-2025-10-19"], id="opus_4_8"),
pytest.param("us.anthropic.claude-opus-5", ["tool-search-tool-2025-10-19"], id="opus_5"),
pytest.param("us.anthropic.claude-sonnet-5", ["tool-search-tool-2025-10-19"], id="sonnet_5"),
pytest.param("us.anthropic.claude-haiku-4-5-20251001-v1:0", ["tool-search-tool-2025-10-19"], id="haiku_4_5"),
pytest.param("us.anthropic.claude-opus-4-1-20250805-v1:0", None, id="opus_4_1_unsupported"),
],
)
def test_bedrock_chat_invoke_tool_search_beta_follows_model_map(
local_model_cost_map, local_beta_headers_config, model, expected_betas
):
"""LIT-5851: the chat Invoke path used to add the ``tool-search-tool-2025-10-19``
beta whenever the id contained ``opus-4``, so Opus 5 and Sonnet 5 lost it, Haiku
4.5 never had it, and Opus 4.1 got it without support. The gate now follows the
model map's ``supports_tool_search`` flag, shared with the messages path."""
result = AmazonAnthropicClaudeConfig().transform_request(
model=model,
messages=[{"role": "user", "content": "Add 2 and 3"}],
optional_params={
"max_tokens": 64,
"tools": [
{"type": "tool_search_tool_regex_20251119", "name": "tool_search_tool_regex"},
{
"type": "function",
"function": {
"name": "add_numbers",
"description": "Add two integers",
"parameters": {
"type": "object",
"properties": {"a": {"type": "integer"}, "b": {"type": "integer"}},
"required": ["a", "b"],
},
},
},
],
},
litellm_params={},
headers={},
)
assert result.get("anthropic_beta") == expected_betas

View file

@ -2649,17 +2649,6 @@ def test_bedrock_clear_thinking_leaves_enabled_thinking_on_non_adaptive_model():
assert "output_config" not in request
@pytest.fixture
def local_beta_headers_config(monkeypatch):
from litellm.anthropic_beta_headers_manager import reload_beta_headers_config
monkeypatch.setenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", "True")
reload_beta_headers_config()
yield
monkeypatch.delenv("LITELLM_LOCAL_ANTHROPIC_BETA_HEADERS", raising=False)
reload_beta_headers_config()
def test_bedrock_messages_preserves_clear_tool_uses_context_management_and_adds_beta(
local_beta_headers_config,
):
@ -2825,9 +2814,12 @@ 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):
def test_bedrock_messages_tool_search_adds_beta_header(local_model_cost_map, local_beta_headers_config, model):
"""
LIT-4522: Bedrock InvokeModel only admits ``tool_search_tool_*`` tool types
when the request body carries the ``tool-search-tool-2025-10-19`` beta;
@ -2837,6 +2829,11 @@ 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.
LIT-5851: the same allowlist then missed Opus 4.8, Opus 5 and Sonnet 5, so
the gate now reads the model map's ``supports_tool_search`` flag (explicit
on the Bedrock entries, and the ``claude-tool-search`` rule for Claude 4.5
and newer) instead of a per-model name list.
"""
from litellm.types.router import GenericLiteLLMParams
@ -2870,10 +2867,10 @@ def test_bedrock_messages_tool_search_adds_beta_header(local_beta_headers_config
def test_bedrock_messages_tool_search_model_map_flag_is_authoritative(local_model_cost_map, monkeypatch):
"""``supports_tool_search`` lives in the model map; the name patterns in
``_supports_tool_search_on_bedrock`` are only a fallback for ids the map
cannot resolve. Flipping the mapped entry's flag to ``False`` must win even
though the model name still matches the ``haiku-4-5`` pattern."""
"""``supports_tool_search`` lives in the model map; the ``claude-tool-search``
rule only fills entries that carry no opinion. Flipping the mapped entry's
flag to ``False`` must win even though the id is a Claude 4.5 the rule
would flag."""
import litellm
from litellm.llms.anthropic.common_utils import AnthropicModelInfo
@ -2892,19 +2889,43 @@ def test_bedrock_messages_tool_search_model_map_flag_is_authoritative(local_mode
@pytest.mark.parametrize(
"model, expected",
[
pytest.param("us.anthropic.claude-opus-4-6-v99:9", True, id="unmapped_id_falls_back_to_patterns"),
pytest.param("anthropic.claude-3-5-sonnet-20240620-v1:0", False, id="mapped_entry_without_flag_no_pattern"),
pytest.param("us.anthropic.claude-opus-4-6-v99:9", True, id="unmapped_4_6_variant"),
pytest.param("us.anthropic.claude-haiku-5-2", True, id="unmapped_future_minor"),
pytest.param(
"arn:aws:bedrock:us-east-1:123456789012:inference-profile/us.anthropic.claude-opus-5",
True,
id="inference_profile_arn",
),
pytest.param("anthropic.claude-3-5-sonnet-20240620-v1:0", False, id="mapped_claude_3_5_without_flag"),
pytest.param("us.anthropic.claude-opus-4-1-20250805-v1:0", False, id="mapped_opus_4_1_without_flag"),
pytest.param("us.anthropic.claude-sonnet-4-20250514-v1:0", False, id="mapped_dated_sonnet_4_without_flag"),
],
)
def test_bedrock_messages_tool_search_pattern_fallback(local_model_cost_map, model, expected):
"""Ids the model map cannot resolve (or resolves without a
``supports_tool_search`` opinion) fall through to the name patterns, so
ARNs and unlisted regional variants of supported families keep working."""
def test_bedrock_messages_tool_search_follows_claude_tool_search_rule(local_model_cost_map, model, expected):
"""Ids the model map cannot resolve, or resolves without a ``supports_tool_search``
opinion, take the ``claude-tool-search`` fallback rule: Claude 4.5 and newer get
the beta, ARNs and unlisted regional variants included, and older Claudes do not."""
cfg = AmazonAnthropicClaudeMessagesConfig()
assert cfg._supports_tool_search_on_bedrock(model) is expected
def test_bedrock_messages_tool_search_rule_fills_mapped_entry_without_flag(local_model_cost_map, monkeypatch):
"""LIT-5851: a Bedrock entry that is in the map but carries no ``supports_tool_search``
key, the state Opus 4.8, Opus 5 and Sonnet 5 shipped in, is filled by the
``claude-tool-search`` rule instead of resolving to ``None`` and losing the beta."""
import litellm
model = "us.anthropic.claude-opus-5"
cfg = AmazonAnthropicClaudeMessagesConfig()
monkeypatch.delitem(litellm.model_cost[model], "supports_tool_search")
litellm.get_model_info.cache_clear()
assert litellm.get_model_info(model, custom_llm_provider="bedrock")["supports_tool_search"] is True
assert cfg._supports_tool_search_on_bedrock(model) is True
def test_bedrock_messages_thinking_shape_follows_exact_bedrock_entry_flag(
local_model_cost_map, monkeypatch
):