diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 775eb6eda39..19d4975ce77 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -2493,7 +2493,7 @@ class AnthropicConfig(AnthropicModelInfo, BaseConfig): # Anthropic errors with: "When thinking is disabled, an assistant message cannot contain thinking" # Related issue: https://github.com/BerriAI/litellm/issues/18926 if ( - optional_params.get("thinking") is not None + optional_params.get("thinking") not in (None, {"type": "disabled"}) and last_assistant_with_tool_calls_has_no_thinking_blocks(capped_messages) and not any_assistant_message_has_thinking_blocks(capped_messages) ): diff --git a/litellm/llms/anthropic/common_utils.py b/litellm/llms/anthropic/common_utils.py index 1c42dd15853..583164a1930 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -102,6 +102,11 @@ DROP_DISABLED_THINKING_WARNING: Final = ( "disabled (the alternative is a provider 400). The model will still think adaptively, its response can contain " "thinking blocks, and those thinking tokens are billed as output tokens." ) +REMAP_DISABLED_TO_BETWEEN_TOOLS_WARNING: Final = ( + "Replacing `thinking={'type': 'disabled'}` with `thinking={'type': 'between_tools'}` for model=%s: this model " + "400s on `disabled`, and `between_tools` is its lowest thinking setting. The model will not think before " + "responding; progress notes it writes between tool calls still come back as `thinking` blocks." +) # Anthropic error `type` (both the JSON error body and SSE `event: error` # payloads use this field) mapped to the HTTP status code it corresponds to. @@ -1164,6 +1169,28 @@ class AnthropicModelInfo(BaseLLMModelInfo): """ return AnthropicModelInfo._supports_model_capability(model, "thinking_always_on", custom_llm_provider) + @staticmethod + def _supports_between_tools_thinking(model: str, custom_llm_provider: str) -> bool: + """Whether ``model`` accepts ``thinking.type=between_tools`` as its off-equivalent + setting for an always-on-thinking model. The model cost map is authoritative: an + explicit ``supports_between_tools_thinking`` entry resolved under + ``custom_llm_provider``. Sonnet 5.5 is the only family with this entry today; + other always-on-thinking families fall back to the drop-and-warn path in + ``maybe_drop_disabled_thinking`` until their own off-equivalent is confirmed. + """ + return AnthropicModelInfo._supports_model_capability( + model, "supports_between_tools_thinking", custom_llm_provider + ) + + @staticmethod + def _effort_rejects_between_tools(optional_params: Mapping[str, object]) -> bool: + """``thinking.type=between_tools`` 400s at ``xhigh``/``max`` effort; those levels + keep adaptive thinking as their only valid off-equivalent.""" + output_config: Final = optional_params.get("output_config") + if not isinstance(output_config, dict): + return False + return "effort" in output_config and output_config["effort"] in ("xhigh", "max") + @staticmethod def thinks_by_default(model: str, custom_llm_provider: str) -> bool: return AnthropicModelInfo._is_always_on_thinking_model( @@ -1208,14 +1235,24 @@ class AnthropicModelInfo(BaseLLMModelInfo): optional_params: MutableMapping[str, object], # mutable-ok: in-place out-param, as in _maybe_drop_speed_param custom_llm_provider: str, ) -> None: - """Omit ``thinking={'type': 'disabled'}`` for always-on-thinking models - (Fable 5 / Mythos 5), which 400 on it; omission is the API-documented - remedy and yields the model's default adaptive thinking.""" + """Replace ``thinking={'type': 'disabled'}`` for always-on-thinking models + (Fable 5 / Mythos 5, Sonnet 5.5, Opus 5.5), which 400 on it. Sonnet 5.5 has a + real off-equivalent, ``between_tools``, valid at `low`/`medium`/`high` effort; + remap to that instead of silently leaving thinking on at whatever effort the + caller set. Every other always-on-thinking family falls back to omitting + ``thinking``, the API-documented remedy, which yields the model's default + adaptive thinking.""" thinking: Final = optional_params.get("thinking") if not isinstance(thinking, dict) or thinking.get("type") != "disabled": return if not AnthropicModelInfo._is_always_on_thinking_model(model, custom_llm_provider): return + if AnthropicModelInfo._supports_between_tools_thinking( + model, custom_llm_provider + ) and not AnthropicModelInfo._effort_rejects_between_tools(optional_params): + litellm.verbose_logger.warning(REMAP_DISABLED_TO_BETWEEN_TOOLS_WARNING, model) + optional_params["thinking"] = {"type": "between_tools"} # rebind-ok: in-place out-param, see signature + return litellm.verbose_logger.warning( DROP_DISABLED_THINKING_WARNING, model, diff --git a/litellm/llms/bedrock/chat/converse_transformation.py b/litellm/llms/bedrock/chat/converse_transformation.py index 12c5031628b..ca4af4cabd8 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -2166,7 +2166,7 @@ class AmazonConverseConfig(BaseConfig): # If any message has thinking_blocks, we must keep thinking enabled, otherwise # Related issues: https://github.com/BerriAI/litellm/issues/14194 if ( - optional_params.get("thinking") is not None + optional_params.get("thinking") not in (None, {"type": "disabled"}) and messages is not None and last_assistant_with_tool_calls_has_no_thinking_blocks(messages) and not any_assistant_message_has_thinking_blocks(messages) diff --git a/litellm/model_prices_and_context_window_backup.json b/litellm/model_prices_and_context_window_backup.json index b08b72a5434..6ad10850dd6 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -16619,6 +16619,7 @@ "supports_output_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://platform.claude.com/docs/en/about-claude/pricing", "supports_web_search": true @@ -62197,6 +62198,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -70343,6 +70345,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "supports_adaptive_thinking": true, "supports_assistant_prefill": false, @@ -75951,6 +75954,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -75982,6 +75986,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "cache_creation_input_token_cost_above_1hr": 4.4e-06, "cache_creation_input_token_cost": 2.75e-06, @@ -76033,6 +76038,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76068,6 +76074,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://management.azure.com/subscriptions/c873328e-b572-4770-8dff-aaeb6f1f0e79/providers/Microsoft.CognitiveServices/locations/eastus2/models?api-version=2024-10-01" }, @@ -76086,6 +76093,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76122,6 +76130,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76186,6 +76195,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76232,6 +76242,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76273,6 +76284,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76383,6 +76395,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76452,6 +76465,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76491,6 +76505,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing" }, @@ -76530,6 +76545,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing" }, diff --git a/litellm/types/llms/anthropic.py b/litellm/types/llms/anthropic.py index c322eb25b3e..9ddba8280eb 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -816,7 +816,7 @@ class AnthropicThinkingBlockBinding(TypedDict, total=False): class AnthropicThinkingParam(TypedDict, total=False): - type: ReadOnly[Literal["enabled", "adaptive", "disabled"]] + type: ReadOnly[Literal["enabled", "adaptive", "disabled", "between_tools"]] budget_tokens: int display: ReadOnly[Literal["summarized", "omitted", "updates"]] block_binding: ReadOnly[AnthropicThinkingBlockBinding] diff --git a/litellm/types/utils.py b/litellm/types/utils.py index 3ddeb118c33..d2deab3ffb5 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -225,6 +225,7 @@ class ProviderSpecificModelInfo(TypedDict, total=False): supports_adaptive_thinking: bool | None supports_legacy_thinking: ReadOnly[bool | None] thinking_always_on: ReadOnly[bool | None] + supports_between_tools_thinking: ReadOnly[bool | None] thinking_on_by_default: ReadOnly[bool | None] supports_tool_search: bool | None supports_per_turn_control: ReadOnly[bool | None] diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index b08b72a5434..6ad10850dd6 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -16619,6 +16619,7 @@ "supports_output_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://platform.claude.com/docs/en/about-claude/pricing", "supports_web_search": true @@ -62197,6 +62198,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -70343,6 +70345,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "supports_adaptive_thinking": true, "supports_assistant_prefill": false, @@ -75951,6 +75954,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -75982,6 +75986,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "cache_creation_input_token_cost_above_1hr": 4.4e-06, "cache_creation_input_token_cost": 2.75e-06, @@ -76033,6 +76038,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76068,6 +76074,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://management.azure.com/subscriptions/c873328e-b572-4770-8dff-aaeb6f1f0e79/providers/Microsoft.CognitiveServices/locations/eastus2/models?api-version=2024-10-01" }, @@ -76086,6 +76093,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76122,6 +76130,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76186,6 +76195,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76232,6 +76242,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76273,6 +76284,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76383,6 +76395,7 @@ "output_cost_per_token": 1.2e-05, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json", "supports_adaptive_thinking": true, @@ -76452,6 +76465,7 @@ "supports_parallel_tool_use_config": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://pricing.us-east-1.amazonaws.com/offers/v1.0/aws/AmazonBedrockFoundationModels/current/index.json" }, @@ -76491,6 +76505,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing" }, @@ -76530,6 +76545,7 @@ "supports_max_reasoning_effort": true, "supports_forced_tool_use": false, "thinking_always_on": true, + "supports_between_tools_thinking": true, "prompt_cache_min_tokens": 512, "source": "https://cloud.google.com/gemini-enterprise-agent-platform/generative-ai/pricing" }, diff --git a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py index ae72af200d4..6237c3bec38 100644 --- a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -7776,6 +7776,32 @@ def test_chat_dummy_tool_result_for_an_orphaned_tool_call_replays_a_byte_identic assert requests[0]["messages"][2]["content"][0]["type"] == "tool_result" +def test_disabled_thinking_on_sonnet_5_5_tool_result_turn_remaps_to_between_tools(local_model_cost_map, monkeypatch): + monkeypatch.setattr(litellm, "modify_params", True) + messages: Final = [ + {"role": "user", "content": "Look it up"}, + { + "role": "assistant", + "content": None, + "tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "lookup", "arguments": "{}"}}], + }, + {"role": "tool", "tool_call_id": "call_1", "content": "found"}, + ] + tools: Final = [ + {"name": "lookup", "description": "Look something up", "input_schema": {"type": "object", "properties": {}}} + ] + + request: Final = AnthropicConfig().transform_request( + model="claude-sonnet-5-5", + messages=messages, + optional_params={"tools": tools, "thinking": {"type": "disabled"}}, + litellm_params={}, + headers={}, + ) + + assert request["thinking"] == {"type": "between_tools"} + + anthropic_chunk_list: Final = [ { "type": "content_block_start", diff --git a/tests/unit/llms/anthropic/test_anthropic_common_utils.py b/tests/unit/llms/anthropic/test_anthropic_common_utils.py index 14cabb683f1..0aed15a7b0b 100644 --- a/tests/unit/llms/anthropic/test_anthropic_common_utils.py +++ b/tests/unit/llms/anthropic/test_anthropic_common_utils.py @@ -4541,6 +4541,59 @@ def test_tool_changes_beta_requires_system_tool_reference(role: str, content: ob assert ANTHROPIC_MID_CONVERSATION_TOOL_CHANGES_BETA_HEADER not in headers.get("anthropic-beta", "").split(",") +@pytest.mark.parametrize("effort", (None, "low", "medium", "high")) +def test_maybe_drop_disabled_thinking_remaps_sonnet_5_5_to_between_tools( + local_model_cost_map: None, effort: str | None +) -> None: + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + optional_params: Final[dict[str, object]] = {"thinking": {"type": "disabled"}} + if effort is not None: + optional_params["output_config"] = {"effort": effort} + + AnthropicModelInfo.maybe_drop_disabled_thinking( + model="claude-sonnet-5-5", + optional_params=optional_params, + custom_llm_provider="anthropic", + ) + + assert optional_params["thinking"] == {"type": "between_tools"} + + +@pytest.mark.parametrize("effort", ("xhigh", "max")) +def test_maybe_drop_disabled_thinking_drops_for_sonnet_5_5_at_rejecting_effort( + local_model_cost_map: None, effort: str +) -> None: + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + optional_params: Final[dict[str, object]] = { + "thinking": {"type": "disabled"}, + "output_config": {"effort": effort}, + } + + AnthropicModelInfo.maybe_drop_disabled_thinking( + model="claude-sonnet-5-5", + optional_params=optional_params, + custom_llm_provider="anthropic", + ) + + assert "thinking" not in optional_params + + +def test_maybe_drop_disabled_thinking_still_drops_for_opus_5_5(local_model_cost_map: None) -> None: + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + optional_params: Final[dict[str, object]] = {"thinking": {"type": "disabled"}} + + AnthropicModelInfo.maybe_drop_disabled_thinking( + model="claude-opus-5-5", + optional_params=optional_params, + custom_llm_provider="anthropic", + ) + + assert "thinking" not in optional_params + + @pytest.mark.parametrize( ("tool_call_id", "provider_specific_fields", "rebuilt"), ( diff --git a/tests/unit/llms/bedrock/chat/test_converse_transformation.py b/tests/unit/llms/bedrock/chat/test_converse_transformation.py index 4dcf312737d..e4ea2157b1f 100644 --- a/tests/unit/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/unit/llms/bedrock/chat/test_converse_transformation.py @@ -8808,6 +8808,32 @@ def test_disabled_thinking_omitted_for_always_on_models_converse(local_model_cos assert additional.get("thinking") == {"type": "disabled"} +def test_disabled_thinking_on_sonnet_5_5_tool_result_turn_remaps_to_between_tools_converse( + local_model_cost_map, monkeypatch +): + monkeypatch.setattr(litellm, "modify_params", True) + messages: Final = [ + {"role": "user", "content": "Look it up"}, + { + "role": "assistant", + "content": None, + "tool_calls": [{"id": "call_1", "type": "function", "function": {"name": "lookup", "arguments": "{}"}}], + }, + {"role": "tool", "tool_call_id": "call_1", "content": "found"}, + ] + tools: Final = [{"type": "function", "function": {"name": "lookup", "parameters": {"type": "object"}}}] + + result: Final = AmazonConverseConfig()._transform_request( + model="us.anthropic.claude-sonnet-5-5", + messages=messages, + optional_params={"maxTokens": 64, "tools": tools, "thinking": {"type": "disabled"}}, + litellm_params={}, + headers={}, + ) + + assert result.get("additionalModelRequestFields", {}).get("thinking") == {"type": "between_tools"} + + @pytest.mark.parametrize( "model", ["anthropic.claude-fable-5-1", "us.anthropic.claude-fable-5-1"], diff --git a/tests/unit/test_claude_sonnet_5_config.py b/tests/unit/test_claude_sonnet_5_config.py index eb040ceae1b..12f83b51d1b 100644 --- a/tests/unit/test_claude_sonnet_5_config.py +++ b/tests/unit/test_claude_sonnet_5_config.py @@ -10,8 +10,10 @@ populate ``litellm.anthropic_models`` at import, which is what lets a bare ``anthropic/*`` wildcard deployment). Sonnet 5.5 (``claude-sonnet-5-5``) is covered here too. It carries the same -gen-5 profile but thinking cannot be turned off and forced tool use is not -supported, same as Opus 5.5 +gen-5 profile, and forced tool use is not supported, same as Opus 5.5. Thinking +cannot be turned off via ``thinking.type=disabled``, the Sonnet 5 shape; the +off-equivalent is ``thinking.type=between_tools``, which ``maybe_drop_disabled_thinking`` +remaps a caller's ``disabled`` request to. """ import json @@ -78,9 +80,29 @@ def test_sonnet_5_5_present_in_bundled_backup(model_name): ) def test_sonnet_5_5_thinking_profile(local_model_cost_map, model, provider): """Sonnet 5.5 has thinking always on with the adaptive thinking surface, and - no forced tool use, same as Opus 5.5.""" + no forced tool use, same as Opus 5.5. Unlike the rest of that family, it also + has a real off-equivalent for thinking: between_tools.""" from litellm.llms.anthropic.common_utils import AnthropicModelInfo assert AnthropicModelInfo.is_adaptive_thinking_model(model, provider) is True assert AnthropicModelInfo._is_always_on_thinking_model(model, provider) is True + assert AnthropicModelInfo._supports_between_tools_thinking(model, provider) is True assert AnthropicModelInfo.forced_tool_use_unsupported(model.removeprefix("anthropic/")) is True + + +@pytest.mark.parametrize( + ("model", "provider"), + [ + ("claude-opus-5-5", "anthropic"), + ("claude-fable-5", "anthropic"), + ("claude-mythos-5", "anthropic"), + ], +) +def test_other_always_on_thinking_models_lack_between_tools(local_model_cost_map, model, provider): + """Sonnet 5.5 is the only always-on-thinking family with a confirmed + between_tools off-equivalent today; the rest must keep falling back to the + drop-and-warn path in maybe_drop_disabled_thinking.""" + from litellm.llms.anthropic.common_utils import AnthropicModelInfo + + assert AnthropicModelInfo._is_always_on_thinking_model(model, provider) is True + assert AnthropicModelInfo._supports_between_tools_thinking(model, provider) is False diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 700307f48f5..72d05d2222a 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1246,6 +1246,7 @@ _COST_MAP_ROW_SCHEMA: Final = { "supports_anthropic_thinking_payload": {"type": "boolean"}, "supports_legacy_thinking": {"type": "boolean"}, "thinking_always_on": {"type": "boolean"}, + "supports_between_tools_thinking": {"type": "boolean"}, "thinking_on_by_default": {"type": "boolean"}, "supports_mid_conversation_system": {"type": "boolean"}, "supports_sampling_params": {"type": "boolean"},