mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
fix(adapter): normalize reasoning effort with graceful degradation (#26111)
* fix(model-info): include reasoning effort support fields in get_model_info _get_model_info_helper constructs ModelInfoBase explicitly but never reads supports_xhigh/minimal/none_reasoning_effort from the cost map JSON. Add the three fields so get_model_info() returns them correctly. Also add supports_minimal_reasoning_effort to the ModelInfo TypedDict (xhigh and none were already declared, minimal was missing). * fix(model-registry): add missing reasoning effort fields for claude 4.6/4.7 Claude Opus 4.7 supports max reasoning effort (above xhigh). The field was present for Opus 4.6 but missing for all Opus 4.7 entries (base, dated, Bedrock, Vertex AI, Azure AI). All Claude 4.6/4.7 models (Opus 4.6, Sonnet 4.6, Opus 4.7) support minimal reasoning effort via adaptive thinking. Add the field to all provider variants. * fix(adapter): map output_config.effort to reasoning_effort (#25079) Anthropic's adaptive thinking (thinking.type="adaptive") and output_config.effort were silently dropped when translating to OpenAI format, resulting in no reasoning_effort on the outgoing request. Adapter changes (format translation): - adapters/transformation.py: add "adaptive" branch to translate_anthropic_thinking_to_reasoning_effort(); pass through output_config.effort as-is in _translate_thinking_to_openai(); add "output_config" to translatable_anthropic_params - adapters/handler.py: extract output_config from extra_kwargs into request_data so it reaches the translation layer - responses_adapters/transformation.py: add "adaptive" branch and output_config param to translate_thinking_to_reasoning() Handler changes (model-aware normalization): - utils.py: add normalize_reasoning_effort_value() that uses get_model_info() to map "max" → "xhigh"/"high" and "minimal" → "minimal"/"low" based on model capabilities - adapters/handler.py: call normalization before responses routing - responses_adapters/handler.py: call normalization after translation Relates to BerriAI/litellm#25079 * test(reasoning-effort): add tests for effort capability fields and normalize logic Test coverage for: - get_model_info returning supports_minimal/max_reasoning_effort fields - JSON registry entries for claude 4.6/4.7 across all providers - normalize_reasoning_effort_value degradation chains and exception fallback - Adapter translation of adaptive thinking + output_config.effort * fix: forward custom_llm_provider to normalize_reasoning_effort_value in responses adapter
This commit is contained in:
parent
034f4fdef2
commit
b42b86df7a
10 changed files with 598 additions and 74 deletions
|
|
@ -106,6 +106,44 @@ class LiteLLMMessagesToCompletionTransformationHandler:
|
|||
updated_reasoning_effort["summary"] = effective_summary
|
||||
completion_kwargs["reasoning_effort"] = updated_reasoning_effort
|
||||
|
||||
@staticmethod
|
||||
def _normalize_reasoning_effort(
|
||||
completion_kwargs: Dict[str, Any],
|
||||
) -> None:
|
||||
"""
|
||||
Normalize reasoning_effort values based on target model capabilities.
|
||||
|
||||
Handles both string ("max") and dict ({"effort": "max", "summary": ...})
|
||||
formats. Uses model registry to check supports_xhigh/supports_minimal.
|
||||
"""
|
||||
from litellm.llms.anthropic.experimental_pass_through.utils import (
|
||||
normalize_reasoning_effort_value,
|
||||
)
|
||||
|
||||
reasoning_effort = completion_kwargs.get("reasoning_effort")
|
||||
if reasoning_effort is None:
|
||||
return
|
||||
|
||||
model = cast(str, completion_kwargs.get("model", ""))
|
||||
custom_llm_provider = completion_kwargs.get("custom_llm_provider")
|
||||
|
||||
if isinstance(reasoning_effort, str):
|
||||
normalized = normalize_reasoning_effort_value(
|
||||
reasoning_effort, model=model, custom_llm_provider=custom_llm_provider
|
||||
)
|
||||
if normalized != reasoning_effort:
|
||||
completion_kwargs["reasoning_effort"] = normalized
|
||||
elif isinstance(reasoning_effort, dict) and "effort" in reasoning_effort:
|
||||
effort = reasoning_effort["effort"]
|
||||
normalized = normalize_reasoning_effort_value(
|
||||
effort, model=model, custom_llm_provider=custom_llm_provider
|
||||
)
|
||||
if normalized != effort:
|
||||
completion_kwargs["reasoning_effort"] = {
|
||||
**reasoning_effort,
|
||||
"effort": normalized,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _prepare_completion_kwargs(
|
||||
*,
|
||||
|
|
@ -163,6 +201,12 @@ class LiteLLMMessagesToCompletionTransformationHandler:
|
|||
if output_format:
|
||||
request_data["output_format"] = output_format
|
||||
|
||||
# Extract output_config from extra_kwargs so the translator can use it
|
||||
# (e.g. output_config.effort for adaptive thinking → reasoning_effort)
|
||||
extra_kwargs = extra_kwargs or {}
|
||||
if "output_config" in extra_kwargs:
|
||||
request_data["output_config"] = extra_kwargs["output_config"]
|
||||
|
||||
(
|
||||
openai_request,
|
||||
tool_name_mapping,
|
||||
|
|
@ -202,6 +246,14 @@ class LiteLLMMessagesToCompletionTransformationHandler:
|
|||
):
|
||||
completion_kwargs[key] = value
|
||||
|
||||
# Normalize reasoning_effort based on model capabilities
|
||||
# (e.g. "max" → "xhigh"/"high", "minimal" → "low" if unsupported)
|
||||
# Must run BEFORE _route_openai_thinking, which prepends "responses/"
|
||||
# to the model name and would break get_model_info() lookups.
|
||||
LiteLLMMessagesToCompletionTransformationHandler._normalize_reasoning_effort(
|
||||
completion_kwargs
|
||||
)
|
||||
|
||||
LiteLLMMessagesToCompletionTransformationHandler._route_openai_thinking_to_responses_api_if_needed(
|
||||
completion_kwargs,
|
||||
thinking=thinking,
|
||||
|
|
|
|||
|
|
@ -317,6 +317,7 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
"tools",
|
||||
"thinking",
|
||||
"output_format",
|
||||
"output_config",
|
||||
]
|
||||
|
||||
def _is_web_search_tool(self, tool: Dict[str, Any]) -> bool:
|
||||
|
|
@ -694,6 +695,11 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
return "low"
|
||||
else:
|
||||
return "minimal"
|
||||
elif thinking_type == "adaptive":
|
||||
# Adaptive thinking: effort is controlled by output_config.effort,
|
||||
# not budget_tokens. Return a default; caller should override with
|
||||
# output_config.effort when available.
|
||||
return "medium"
|
||||
|
||||
return None
|
||||
|
||||
|
|
@ -1041,6 +1047,12 @@ class LiteLLMAnthropicMessagesAdapter:
|
|||
if not reasoning_effort:
|
||||
return
|
||||
|
||||
# For adaptive thinking, override with output_config.effort if available
|
||||
if isinstance(thinking, dict) and thinking.get("type") == "adaptive":
|
||||
output_config = anthropic_message_request.get("output_config")
|
||||
if isinstance(output_config, dict) and output_config.get("effort"):
|
||||
reasoning_effort = output_config["effort"]
|
||||
|
||||
summary = thinking.get("summary") if isinstance(thinking, dict) else None
|
||||
auto_summary = is_reasoning_auto_summary_enabled()
|
||||
if summary:
|
||||
|
|
|
|||
|
|
@ -72,6 +72,23 @@ def _build_responses_kwargs(
|
|||
anthropic_request = AnthropicMessagesRequest(**request_data) # type: ignore[typeddict-item]
|
||||
responses_kwargs = _ADAPTER.translate_request(anthropic_request)
|
||||
|
||||
# Normalize reasoning effort based on model capabilities
|
||||
# (e.g. "max" → "xhigh"/"high", "minimal" → "low" if unsupported)
|
||||
reasoning = responses_kwargs.get("reasoning")
|
||||
if isinstance(reasoning, dict) and "effort" in reasoning:
|
||||
from litellm.llms.anthropic.experimental_pass_through.utils import (
|
||||
normalize_reasoning_effort_value,
|
||||
)
|
||||
|
||||
effort = reasoning["effort"]
|
||||
normalized = normalize_reasoning_effort_value(
|
||||
effort,
|
||||
model=model,
|
||||
custom_llm_provider=(extra_kwargs or {}).get("custom_llm_provider"),
|
||||
)
|
||||
if normalized != effort:
|
||||
responses_kwargs["reasoning"] = {**reasoning, "effort": normalized}
|
||||
|
||||
if stream:
|
||||
responses_kwargs["stream"] = True
|
||||
|
||||
|
|
|
|||
|
|
@ -251,25 +251,41 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
|
|||
|
||||
@staticmethod
|
||||
def translate_thinking_to_reasoning(
|
||||
thinking: Dict[str, Any]
|
||||
thinking: Dict[str, Any],
|
||||
output_config: Optional[Dict[str, Any]] = None,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
Convert Anthropic thinking param to Responses API reasoning param.
|
||||
|
||||
thinking.budget_tokens maps to reasoning effort:
|
||||
>= 10000 -> high, >= 5000 -> medium, >= 2000 -> low, < 2000 -> minimal
|
||||
|
||||
For adaptive thinking, uses output_config.effort if available,
|
||||
otherwise defaults to medium.
|
||||
"""
|
||||
if not isinstance(thinking, dict) or thinking.get("type") != "enabled":
|
||||
if not isinstance(thinking, dict):
|
||||
return None
|
||||
budget = thinking.get("budget_tokens", 0)
|
||||
if budget >= 10000:
|
||||
effort = "high"
|
||||
elif budget >= 5000:
|
||||
|
||||
thinking_type = thinking.get("type")
|
||||
|
||||
if thinking_type == "adaptive":
|
||||
# Use output_config.effort if available
|
||||
effort = "medium"
|
||||
elif budget >= 2000:
|
||||
effort = "low"
|
||||
if isinstance(output_config, dict) and output_config.get("effort"):
|
||||
effort = output_config["effort"]
|
||||
elif thinking_type == "enabled":
|
||||
budget = thinking.get("budget_tokens", 0)
|
||||
if budget >= 10000:
|
||||
effort = "high"
|
||||
elif budget >= 5000:
|
||||
effort = "medium"
|
||||
elif budget >= 2000:
|
||||
effort = "low"
|
||||
else:
|
||||
effort = "minimal"
|
||||
else:
|
||||
effort = "minimal"
|
||||
return None
|
||||
|
||||
auto_summary = is_reasoning_auto_summary_enabled()
|
||||
result: Dict[str, Any] = {"effort": effort}
|
||||
summary = thinking.get("summary")
|
||||
|
|
@ -346,7 +362,11 @@ class LiteLLMAnthropicToResponsesAPIAdapter:
|
|||
# thinking -> reasoning
|
||||
thinking = anthropic_request.get("thinking")
|
||||
if isinstance(thinking, dict):
|
||||
reasoning = self.translate_thinking_to_reasoning(thinking)
|
||||
output_config = anthropic_request.get("output_config")
|
||||
reasoning = self.translate_thinking_to_reasoning(
|
||||
thinking,
|
||||
output_config=cast(Optional[Dict[str, Any]], output_config),
|
||||
)
|
||||
if reasoning:
|
||||
responses_kwargs["reasoning"] = reasoning
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import os
|
||||
from typing import Optional
|
||||
|
||||
import litellm
|
||||
|
||||
|
|
@ -9,3 +10,46 @@ def is_reasoning_auto_summary_enabled() -> bool:
|
|||
litellm.reasoning_auto_summary
|
||||
or os.getenv("LITELLM_REASONING_AUTO_SUMMARY", "false").lower() == "true"
|
||||
)
|
||||
|
||||
|
||||
def normalize_reasoning_effort_value(
|
||||
effort: str,
|
||||
model: str,
|
||||
custom_llm_provider: Optional[str] = None,
|
||||
) -> str:
|
||||
"""
|
||||
Normalize a reasoning effort value based on model capabilities.
|
||||
|
||||
Degradation chains:
|
||||
- "max" → max / xhigh / high
|
||||
- "xhigh" → xhigh / high
|
||||
- "minimal" → minimal / low
|
||||
- other values pass through unchanged
|
||||
"""
|
||||
if effort not in ("max", "xhigh", "minimal"):
|
||||
return effort
|
||||
|
||||
from litellm.utils import get_model_info
|
||||
|
||||
try:
|
||||
model_info = get_model_info(
|
||||
model=model, custom_llm_provider=custom_llm_provider
|
||||
)
|
||||
except Exception:
|
||||
model_info = {}
|
||||
|
||||
if effort == "max":
|
||||
if model_info.get("supports_max_reasoning_effort"):
|
||||
return "max"
|
||||
if model_info.get("supports_xhigh_reasoning_effort"):
|
||||
return "xhigh"
|
||||
return "high"
|
||||
elif effort == "xhigh":
|
||||
if model_info.get("supports_xhigh_reasoning_effort"):
|
||||
return "xhigh"
|
||||
return "high"
|
||||
elif effort == "minimal":
|
||||
if model_info.get("supports_minimal_reasoning_effort"):
|
||||
return "minimal"
|
||||
return "low"
|
||||
return "medium"
|
||||
|
|
|
|||
|
|
@ -1006,7 +1006,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"global.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -1034,7 +1035,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1062,7 +1064,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1090,7 +1093,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1118,7 +1122,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -1146,7 +1151,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"global.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -1174,7 +1181,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1202,7 +1211,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1230,7 +1241,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1258,7 +1271,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1285,7 +1300,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"global.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1312,7 +1328,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1339,7 +1356,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1366,7 +1384,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1393,7 +1412,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1911,7 +1931,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure_ai/claude-opus-4-7": {
|
||||
"input_cost_per_token": 5e-06,
|
||||
|
|
@ -1939,7 +1960,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure_ai/claude-opus-4-1": {
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
|
|
@ -2003,7 +2026,8 @@
|
|||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure/computer-use-preview": {
|
||||
"input_cost_per_token": 3e-06,
|
||||
|
|
@ -8909,7 +8933,8 @@
|
|||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-sonnet-4-5-20250929-v1:0": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -9103,7 +9128,8 @@
|
|||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
},
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-6-20260205": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9135,7 +9161,8 @@
|
|||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
},
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9167,7 +9194,9 @@
|
|||
"provider_specific_entry": {
|
||||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
}
|
||||
},
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-7-20260416": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9199,7 +9228,9 @@
|
|||
"provider_specific_entry": {
|
||||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
}
|
||||
},
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-sonnet-4-20250514": {
|
||||
"deprecation_date": "2026-05-14",
|
||||
|
|
@ -25052,7 +25083,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"openrouter/anthropic/claude-opus-4.5": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -25090,7 +25122,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"openrouter/anthropic/claude-sonnet-4.5": {
|
||||
"input_cost_per_image": 0.0048,
|
||||
|
|
@ -30118,7 +30151,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true
|
||||
"supports_vision": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vercel_ai_gateway/anthropic/claude-sonnet-4": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -31345,7 +31379,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-6@default": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31372,7 +31407,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31399,7 +31435,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-7@default": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31426,7 +31464,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-sonnet-4-5": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -31478,7 +31518,8 @@
|
|||
"search_context_size_high": 0.01,
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01
|
||||
}
|
||||
},
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-sonnet-4-5@20250929": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -38345,7 +38386,8 @@
|
|||
"search_context_size_high": 0.01,
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01
|
||||
}
|
||||
},
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"duckduckgo/search": {
|
||||
"litellm_provider": "duckduckgo",
|
||||
|
|
|
|||
|
|
@ -139,7 +139,9 @@ class ProviderSpecificModelInfo(TypedDict, total=False):
|
|||
supports_reasoning: Optional[bool]
|
||||
supports_url_context: Optional[bool]
|
||||
supports_none_reasoning_effort: Optional[bool]
|
||||
supports_minimal_reasoning_effort: Optional[bool]
|
||||
supports_xhigh_reasoning_effort: Optional[bool]
|
||||
supports_max_reasoning_effort: Optional[bool]
|
||||
|
||||
|
||||
class SearchContextCostPerQuery(TypedDict, total=False):
|
||||
|
|
|
|||
|
|
@ -5893,9 +5893,15 @@ def _get_model_info_helper( # noqa: PLR0915
|
|||
supports_none_reasoning_effort=_model_info.get(
|
||||
"supports_none_reasoning_effort", None
|
||||
),
|
||||
supports_minimal_reasoning_effort=_model_info.get(
|
||||
"supports_minimal_reasoning_effort", None
|
||||
),
|
||||
supports_xhigh_reasoning_effort=_model_info.get(
|
||||
"supports_xhigh_reasoning_effort", None
|
||||
),
|
||||
supports_max_reasoning_effort=_model_info.get(
|
||||
"supports_max_reasoning_effort", None
|
||||
),
|
||||
supports_computer_use=_model_info.get("supports_computer_use", None),
|
||||
search_context_cost_per_query=_model_info.get(
|
||||
"search_context_cost_per_query", None
|
||||
|
|
|
|||
|
|
@ -1006,7 +1006,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"global.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -1034,7 +1035,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1062,7 +1064,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1090,7 +1093,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-opus-4-6-v1": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1118,7 +1122,8 @@
|
|||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -1146,7 +1151,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-mythos-preview": {
|
||||
"input_cost_per_token": 0,
|
||||
|
|
@ -1188,7 +1195,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1216,7 +1225,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1244,7 +1255,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.875e-06,
|
||||
|
|
@ -1272,7 +1285,9 @@
|
|||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1299,7 +1314,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"global.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1326,7 +1342,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"us.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1353,7 +1370,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"eu.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1380,7 +1398,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"au.anthropic.claude-sonnet-4-6": {
|
||||
"cache_creation_input_token_cost": 4.125e-06,
|
||||
|
|
@ -1407,7 +1426,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_native_structured_output": true
|
||||
"supports_native_structured_output": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"anthropic.claude-sonnet-4-20250514-v1:0": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -1925,7 +1945,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure_ai/claude-opus-4-7": {
|
||||
"input_cost_per_token": 5e-06,
|
||||
|
|
@ -1953,7 +1974,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure_ai/claude-opus-4-1": {
|
||||
"cache_creation_input_token_cost": 1.875e-05,
|
||||
|
|
@ -2017,7 +2040,8 @@
|
|||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"azure/computer-use-preview": {
|
||||
"input_cost_per_token": 3e-06,
|
||||
|
|
@ -8923,7 +8947,8 @@
|
|||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-sonnet-4-5-20250929-v1:0": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -9117,7 +9142,8 @@
|
|||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
},
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-6-20260205": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9149,7 +9175,8 @@
|
|||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
},
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9181,7 +9208,9 @@
|
|||
"provider_specific_entry": {
|
||||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
}
|
||||
},
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-opus-4-7-20260416": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -9213,7 +9242,9 @@
|
|||
"provider_specific_entry": {
|
||||
"us": 1.1,
|
||||
"fast": 6.0
|
||||
}
|
||||
},
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"claude-sonnet-4-20250514": {
|
||||
"deprecation_date": "2026-05-14",
|
||||
|
|
@ -25066,7 +25097,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 159
|
||||
"tool_use_system_prompt_tokens": 159,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"openrouter/anthropic/claude-opus-4.5": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -25104,7 +25136,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"openrouter/anthropic/claude-sonnet-4.5": {
|
||||
"input_cost_per_image": 0.0048,
|
||||
|
|
@ -30132,7 +30165,8 @@
|
|||
"supports_reasoning": true,
|
||||
"supports_response_schema": true,
|
||||
"supports_tool_choice": true,
|
||||
"supports_vision": true
|
||||
"supports_vision": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vercel_ai_gateway/anthropic/claude-sonnet-4": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -31359,7 +31393,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-6@default": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31386,7 +31421,8 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-7": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31413,7 +31449,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-opus-4-7@default": {
|
||||
"cache_creation_input_token_cost": 6.25e-06,
|
||||
|
|
@ -31440,7 +31478,9 @@
|
|||
"supports_tool_choice": true,
|
||||
"supports_vision": true,
|
||||
"supports_xhigh_reasoning_effort": true,
|
||||
"tool_use_system_prompt_tokens": 346
|
||||
"tool_use_system_prompt_tokens": 346,
|
||||
"supports_max_reasoning_effort": true,
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-sonnet-4-5": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -31492,7 +31532,8 @@
|
|||
"search_context_size_high": 0.01,
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01
|
||||
}
|
||||
},
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"vertex_ai/claude-sonnet-4-5@20250929": {
|
||||
"cache_creation_input_token_cost": 3.75e-06,
|
||||
|
|
@ -38386,7 +38427,8 @@
|
|||
"search_context_size_high": 0.01,
|
||||
"search_context_size_low": 0.01,
|
||||
"search_context_size_medium": 0.01
|
||||
}
|
||||
},
|
||||
"supports_minimal_reasoning_effort": true
|
||||
},
|
||||
"duckduckgo/search": {
|
||||
"litellm_provider": "duckduckgo",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,287 @@
|
|||
"""
|
||||
Tests for reasoning effort capability fields and normalize_reasoning_effort_value.
|
||||
|
||||
Covers:
|
||||
- Commit 1: get_model_info returns supports_minimal/supports_max fields
|
||||
- Commit 2: Model registry entries have correct reasoning effort fields
|
||||
- Commit 3: normalize_reasoning_effort_value degradation chains + adapter translation
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict, Optional
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from litellm.llms.anthropic.experimental_pass_through.utils import (
|
||||
normalize_reasoning_effort_value,
|
||||
)
|
||||
from litellm.utils import get_model_info
|
||||
|
||||
|
||||
def _load_model_registry() -> Dict[str, Any]:
|
||||
"""Load the root model_prices_and_context_window.json."""
|
||||
json_path = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"../../../../../model_prices_and_context_window.json",
|
||||
)
|
||||
with open(json_path) as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commit 1: get_model_info returns supports_minimal and supports_max fields
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestGetModelInfoReasoningEffortFields:
|
||||
"""get_model_info should expose supports_minimal_reasoning_effort and
|
||||
supports_max_reasoning_effort from the model registry."""
|
||||
|
||||
def test_opus_4_6_has_supports_minimal(self):
|
||||
info = get_model_info("claude-opus-4-6")
|
||||
assert "supports_minimal_reasoning_effort" in info
|
||||
|
||||
def test_opus_4_6_has_supports_max(self):
|
||||
info = get_model_info("claude-opus-4-6")
|
||||
assert "supports_max_reasoning_effort" in info
|
||||
|
||||
def test_opus_4_7_has_supports_minimal(self):
|
||||
info = get_model_info("claude-opus-4-7")
|
||||
assert "supports_minimal_reasoning_effort" in info
|
||||
|
||||
def test_opus_4_7_has_supports_max(self):
|
||||
info = get_model_info("claude-opus-4-7")
|
||||
assert "supports_max_reasoning_effort" in info
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commit 2: JSON registry has correct reasoning effort fields
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestModelRegistryReasoningEffortFields:
|
||||
"""Verify specific models have the expected reasoning effort capability
|
||||
values in the JSON registry file."""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _load_registry(self):
|
||||
self.registry = _load_model_registry()
|
||||
|
||||
def test_opus_4_7_supports_max(self):
|
||||
entry = self.registry["claude-opus-4-7"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
|
||||
def test_opus_4_6_supports_max(self):
|
||||
entry = self.registry["claude-opus-4-6"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
|
||||
def test_opus_4_7_supports_minimal(self):
|
||||
entry = self.registry["claude-opus-4-7"]
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_opus_4_6_supports_minimal(self):
|
||||
entry = self.registry["claude-opus-4-6"]
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_sonnet_4_6_supports_minimal(self):
|
||||
entry = self.registry["anthropic.claude-sonnet-4-6"]
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_bedrock_opus_4_7_supports_max(self):
|
||||
entry = self.registry["anthropic.claude-opus-4-7"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_vertex_opus_4_7_supports_max(self):
|
||||
entry = self.registry["vertex_ai/claude-opus-4-7"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_vertex_opus_4_6_supports_max(self):
|
||||
entry = self.registry["vertex_ai/claude-opus-4-6"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_azure_ai_opus_4_6_supports_minimal(self):
|
||||
entry = self.registry["azure_ai/claude-opus-4-6"]
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
def test_azure_ai_opus_4_7_supports_max(self):
|
||||
entry = self.registry["azure_ai/claude-opus-4-7"]
|
||||
assert entry.get("supports_max_reasoning_effort") is True
|
||||
assert entry.get("supports_minimal_reasoning_effort") is True
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commit 3: normalize_reasoning_effort_value
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _mock_model_info(**flags):
|
||||
"""Return a mock model_info dict with given capability flags."""
|
||||
return flags
|
||||
|
||||
|
||||
class TestNormalizeReasoningEffortValue:
|
||||
"""Test degradation chains for normalize_reasoning_effort_value."""
|
||||
|
||||
# --- "max" degradation chain ---
|
||||
|
||||
def test_max_stays_max_when_supported(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(
|
||||
supports_max_reasoning_effort=True,
|
||||
supports_xhigh_reasoning_effort=True,
|
||||
),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("max", model="test") == "max"
|
||||
|
||||
def test_max_degrades_to_xhigh(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(
|
||||
supports_max_reasoning_effort=False,
|
||||
supports_xhigh_reasoning_effort=True,
|
||||
),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("max", model="test") == "xhigh"
|
||||
|
||||
def test_max_degrades_to_high(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(
|
||||
supports_max_reasoning_effort=False,
|
||||
supports_xhigh_reasoning_effort=False,
|
||||
),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("max", model="test") == "high"
|
||||
|
||||
# --- "xhigh" degradation chain ---
|
||||
|
||||
def test_xhigh_stays_xhigh_when_supported(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(supports_xhigh_reasoning_effort=True),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("xhigh", model="test") == "xhigh"
|
||||
|
||||
def test_xhigh_degrades_to_high(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(supports_xhigh_reasoning_effort=False),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("xhigh", model="test") == "high"
|
||||
|
||||
# --- "minimal" degradation chain ---
|
||||
|
||||
def test_minimal_stays_minimal_when_supported(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(supports_minimal_reasoning_effort=True),
|
||||
):
|
||||
assert (
|
||||
normalize_reasoning_effort_value("minimal", model="test") == "minimal"
|
||||
)
|
||||
|
||||
def test_minimal_degrades_to_low(self):
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
return_value=_mock_model_info(supports_minimal_reasoning_effort=False),
|
||||
):
|
||||
assert normalize_reasoning_effort_value("minimal", model="test") == "low"
|
||||
|
||||
# --- passthrough values ---
|
||||
|
||||
def test_high_passes_through(self):
|
||||
assert normalize_reasoning_effort_value("high", model="test") == "high"
|
||||
|
||||
def test_medium_passes_through(self):
|
||||
assert normalize_reasoning_effort_value("medium", model="test") == "medium"
|
||||
|
||||
def test_low_passes_through(self):
|
||||
assert normalize_reasoning_effort_value("low", model="test") == "low"
|
||||
|
||||
# --- exception fallback ---
|
||||
|
||||
def test_exception_fallback_uses_empty_model_info(self):
|
||||
"""When get_model_info raises, treat model_info as {} (no capabilities)."""
|
||||
with patch(
|
||||
"litellm.utils.get_model_info",
|
||||
side_effect=Exception("model not found"),
|
||||
):
|
||||
# "max" with no capabilities -> "high"
|
||||
assert normalize_reasoning_effort_value("max", model="unknown") == "high"
|
||||
# "minimal" with no capabilities -> "low"
|
||||
assert normalize_reasoning_effort_value("minimal", model="unknown") == "low"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commit 3: Adapter translation — adaptive thinking + output_config.effort
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
class TestAdapterAdaptiveThinking:
|
||||
"""Test that adaptive thinking type maps correctly through the adapters."""
|
||||
|
||||
def test_messages_adapter_adaptive_returns_medium_default(self):
|
||||
"""Adaptive thinking returns 'medium' as default reasoning_effort."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import (
|
||||
LiteLLMAnthropicMessagesAdapter,
|
||||
)
|
||||
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
result = adapter.translate_anthropic_thinking_to_reasoning_effort(
|
||||
{"type": "adaptive"}
|
||||
)
|
||||
assert result == "medium"
|
||||
|
||||
def test_messages_adapter_adaptive_overridden_by_output_config(self):
|
||||
"""For adaptive thinking, output_config.effort overrides reasoning_effort."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.adapters.transformation import (
|
||||
LiteLLMAnthropicMessagesAdapter,
|
||||
)
|
||||
from litellm.types.llms.anthropic import AnthropicMessagesRequest
|
||||
|
||||
adapter = LiteLLMAnthropicMessagesAdapter()
|
||||
request = AnthropicMessagesRequest(
|
||||
model="test-model",
|
||||
messages=[{"role": "user", "content": "hello"}],
|
||||
max_tokens=1024,
|
||||
thinking={"type": "adaptive"},
|
||||
output_config={"effort": "high"},
|
||||
)
|
||||
openai_kwargs, _ = adapter.translate_anthropic_to_openai(request)
|
||||
# reasoning_effort should be set (either as string or dict with effort)
|
||||
re = openai_kwargs.get("reasoning_effort")
|
||||
if isinstance(re, dict):
|
||||
assert re["effort"] == "high"
|
||||
else:
|
||||
assert re == "high"
|
||||
|
||||
def test_responses_adapter_adaptive_with_output_config(self):
|
||||
"""Responses adapter: adaptive thinking + output_config.effort."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.responses_adapters.transformation import (
|
||||
LiteLLMAnthropicToResponsesAPIAdapter,
|
||||
)
|
||||
|
||||
result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
|
||||
thinking={"type": "adaptive"},
|
||||
output_config={"effort": "xhigh"},
|
||||
)
|
||||
assert result is not None
|
||||
assert result["effort"] == "xhigh"
|
||||
|
||||
def test_responses_adapter_adaptive_default_medium(self):
|
||||
"""Responses adapter: adaptive thinking without output_config defaults to medium."""
|
||||
from litellm.llms.anthropic.experimental_pass_through.responses_adapters.transformation import (
|
||||
LiteLLMAnthropicToResponsesAPIAdapter,
|
||||
)
|
||||
|
||||
result = LiteLLMAnthropicToResponsesAPIAdapter.translate_thinking_to_reasoning(
|
||||
thinking={"type": "adaptive"},
|
||||
)
|
||||
assert result is not None
|
||||
assert result["effort"] == "medium"
|
||||
Loading…
Add table
Reference in a new issue