diff --git a/litellm/llms/anthropic/chat/transformation.py b/litellm/llms/anthropic/chat/transformation.py index 09d16181c3a..e617c99bd27 100644 --- a/litellm/llms/anthropic/chat/transformation.py +++ b/litellm/llms/anthropic/chat/transformation.py @@ -2433,7 +2433,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 bfd1341711a..5972da52e53 100644 --- a/litellm/llms/anthropic/common_utils.py +++ b/litellm/llms/anthropic/common_utils.py @@ -84,6 +84,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. @@ -1141,6 +1146,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 _supports_legacy_thinking(model: str, custom_llm_provider: str) -> bool: """Whether ``model`` is an adaptive-thinking model that still accepts legacy @@ -1157,14 +1184,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 056f34f1bf4..1922784b34c 100644 --- a/litellm/llms/bedrock/chat/converse_transformation.py +++ b/litellm/llms/bedrock/chat/converse_transformation.py @@ -2133,7 +2133,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 e771cf12124..5500d07cdc9 100644 --- a/litellm/model_prices_and_context_window_backup.json +++ b/litellm/model_prices_and_context_window_backup.json @@ -16516,6 +16516,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 @@ -61897,6 +61898,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" }, @@ -69801,6 +69803,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, @@ -75371,6 +75374,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" }, @@ -75402,6 +75406,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, @@ -75453,6 +75458,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" }, @@ -75488,6 +75494,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" }, @@ -75506,6 +75513,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, @@ -75542,6 +75550,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, @@ -75606,6 +75615,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" }, @@ -75652,6 +75662,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" }, @@ -75693,6 +75704,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" }, @@ -75803,6 +75815,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, @@ -75872,6 +75885,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" }, @@ -75911,6 +75925,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" }, @@ -75950,6 +75965,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 802f6794839..fc85f196b00 100644 --- a/litellm/types/llms/anthropic.py +++ b/litellm/types/llms/anthropic.py @@ -814,7 +814,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 ee80b63f93b..42e6031987f 100644 --- a/litellm/types/utils.py +++ b/litellm/types/utils.py @@ -224,6 +224,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] supports_tool_search: bool | None supports_mid_conversation_system: bool | None supports_url_context: bool | None diff --git a/model_prices_and_context_window.json b/model_prices_and_context_window.json index e771cf12124..5500d07cdc9 100644 --- a/model_prices_and_context_window.json +++ b/model_prices_and_context_window.json @@ -16516,6 +16516,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 @@ -61897,6 +61898,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" }, @@ -69801,6 +69803,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, @@ -75371,6 +75374,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" }, @@ -75402,6 +75406,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, @@ -75453,6 +75458,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" }, @@ -75488,6 +75494,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" }, @@ -75506,6 +75513,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, @@ -75542,6 +75550,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, @@ -75606,6 +75615,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" }, @@ -75652,6 +75662,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" }, @@ -75693,6 +75704,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" }, @@ -75803,6 +75815,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, @@ -75872,6 +75885,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" }, @@ -75911,6 +75925,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" }, @@ -75950,6 +75965,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 f1c38ef4e64..06c6be78c3c 100644 --- a/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py +++ b/tests/unit/llms/anthropic/chat/test_anthropic_chat_transformation.py @@ -7487,6 +7487,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 99e6438c723..b5d6ab66d50 100644 --- a/tests/unit/llms/anthropic/test_anthropic_common_utils.py +++ b/tests/unit/llms/anthropic/test_anthropic_common_utils.py @@ -4540,6 +4540,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 7c19ef5b751..dad7bd5505e 100644 --- a/tests/unit/llms/bedrock/chat/test_converse_transformation.py +++ b/tests/unit/llms/bedrock/chat/test_converse_transformation.py @@ -8619,6 +8619,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 32063f65765..aac5cdfcf5c 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -1243,6 +1243,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"}, "supports_mid_conversation_system": {"type": "boolean"}, "supports_sampling_params": {"type": "boolean"}, "supports_output_config": {"type": "boolean"},