fix(bedrock): gate Invoke tool search on the model map's supports_tool_search flag

The Bedrock InvokeModel transformations decided whether to send the
tool-search-tool-2025-10-19 beta from hardcoded model name lists (a pattern
list on the messages path, an "opus-4" substring on the chat path), so Opus 4.8,
Opus 5 and Sonnet 5 never got the beta on the messages path, Opus 5 and Sonnet 5
never got it on the chat path, Opus 4.1 got it without support, and
/v1/model/info reported supports_tool_search as unset for all three.

Both paths now read the model map through one shared helper: the Bedrock
entries for Opus 4.8, Opus 5 and Sonnet 5 carry supports_tool_search
explicitly, and a claude-tool-search fallback rule flags Claude 4.5 and newer
for unmapped ids, inference-profile ARNs and mapped entries with no opinion,
so the next Claude gets the beta with no code change. An explicit false on a
resolved entry still wins.
This commit is contained in:
mateo-berri 2026-09-16 18:23:14 -07:00
parent a86be37aa6
commit 47b2479c94
9 changed files with 239 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

@ -898,6 +898,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,
@ -46114,6 +46132,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,
@ -46147,6 +46166,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,
@ -46179,6 +46199,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,
@ -59477,6 +59498,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", "azure_ai", "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 shipped the tool search tool (tool_search_tool_regex, tool_search_tool_bm25) with Opus 4.5, Sonnet 4.5 and Haiku 4.5, and every newer Claude keeps it, so the flag follows the version instead of a per-model list. The Bedrock InvokeModel transformations read this flag to send the tool-search-tool-2025-10-19 beta, so a new Claude gets the beta with no code change.",
"model_info": {
"supports_tool_search": true
}
},
{
"name": "wandb-reasoning-baseline",
"pattern": "^wandb/",
@ -63214,6 +63244,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,
@ -63246,6 +63277,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,
@ -63277,6 +63309,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,
@ -63417,6 +63450,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,
@ -63449,6 +63483,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,
@ -63480,6 +63515,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,
@ -46114,6 +46132,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,
@ -46147,6 +46166,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,
@ -46179,6 +46199,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,
@ -59477,6 +59498,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", "azure_ai", "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 shipped the tool search tool (tool_search_tool_regex, tool_search_tool_bm25) with Opus 4.5, Sonnet 4.5 and Haiku 4.5, and every newer Claude keeps it, so the flag follows the version instead of a per-model list. The Bedrock InvokeModel transformations read this flag to send the tool-search-tool-2025-10-19 beta, so a new Claude gets the beta with no code change.",
"model_info": {
"supports_tool_search": true
}
},
{
"name": "wandb-reasoning-baseline",
"pattern": "^wandb/",
@ -63214,6 +63244,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,
@ -63246,6 +63277,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,
@ -63277,6 +63309,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,
@ -63417,6 +63450,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,
@ -63449,6 +63483,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,
@ -63480,6 +63515,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,45 @@ 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-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, 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 the Claude providers, a mapped pre-4.5 entry stays without one, and a reseller
copy of the same model is not touched."""
for key, model, provider in (
("claude-opus-4-7", "claude-opus-4-7", "anthropic"),
("azure_ai/claude-opus-5", "claude-opus-5", "azure_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"]
assert litellm.get_model_info("claude-opus-4-1", custom_llm_provider="anthropic").get("supports_tool_search") is None
assert match_fill_missing_generalizations("claude-opus-5", "bedrock")["supports_tool_search"] is True
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

@ -2650,17 +2650,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,
):
@ -2826,9 +2815,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;
@ -2838,6 +2830,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
@ -2871,10 +2868,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
@ -2893,19 +2890,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
):