mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
fix(anthropic): send reasoning effort none wherever the Responses transformation accepts it
An openai/ deployment the cost map does not list now gets effort none for disabled thinking on the chat route even when its model_info sets a mode, since the off level now asks whether the loaded cost map lists the model instead of reading the row the router registers. On the Responses bridge the off switch is dropped only where the Responses transformation itself would refuse reasoning.effort, so a slash-named model it accepts gets none again for adaptive effort none and disabled thinking.
This commit is contained in:
parent
927601ac0f
commit
476755238f
4 changed files with 50 additions and 16 deletions
|
|
@ -27,6 +27,7 @@ from litellm.litellm_core_utils.prompt_templates.common_utils import (
|
|||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_capability import deployment_rounds_reasoning_effort
|
||||
from litellm.litellm_core_utils.tool_fields import drops_mcp_servers, tools_without_mcp_servers
|
||||
from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig
|
||||
from litellm.responses.utils import auto_reasoning_summary
|
||||
from litellm.types.integrations.custom_logger import CONVERTED_STREAM_KEYS
|
||||
from litellm.types.llms.anthropic import (
|
||||
|
|
@ -41,7 +42,7 @@ from litellm.types.llms.anthropic_messages.anthropic_response import (
|
|||
AnthropicMessagesResponse,
|
||||
)
|
||||
from litellm.types.llms.openai import REASONING_EFFORT, ResponsesAPIResponse
|
||||
from litellm.types.utils import CountContextManagement
|
||||
from litellm.types.utils import CountContextManagement, LlmProviders
|
||||
from litellm.utils import ProviderConfigManager
|
||||
|
||||
from ..adapters.handler import context_management_explicitly_dropped, request_safeguards_evaluator
|
||||
|
|
@ -52,7 +53,6 @@ from ..convertible_blocks import reject_malformed_tool_result_text
|
|||
from ..safeguards import with_safeguard_results
|
||||
from ..utils import (
|
||||
REASONING_EFFORT_OFF,
|
||||
cost_map_row_lacks_reasoning,
|
||||
litellm_logging_obj_from_kwargs,
|
||||
local_model_name,
|
||||
maps_bare_output_effort,
|
||||
|
|
@ -277,7 +277,7 @@ def _reasoning_for_target(
|
|||
return reasoning
|
||||
if effort == REASONING_EFFORT_OFF:
|
||||
off_level: Final = reasoning_off_level(model, custom_llm_provider, drop_params=drop_params)
|
||||
if off_level is None or cost_map_row_lacks_reasoning(model, custom_llm_provider):
|
||||
if off_level is None or _responses_api_refuses_reasoning_effort(model, custom_llm_provider):
|
||||
return None
|
||||
return {**reasoning_params, "effort": off_level}
|
||||
if drop_params and effort not in _KNOWN_REASONING_EFFORTS:
|
||||
|
|
@ -305,6 +305,12 @@ def _reasoning_for_target(
|
|||
return reasoning if normalized == effort else {**reasoning_params, "effort": normalized}
|
||||
|
||||
|
||||
def _responses_api_refuses_reasoning_effort(model: str, custom_llm_provider: str | None) -> bool:
|
||||
return custom_llm_provider == LlmProviders.OPENAI.value and not OpenAIResponsesAPIConfig.supports_reasoning_param(
|
||||
model
|
||||
)
|
||||
|
||||
|
||||
def _model_excludes_effort(
|
||||
effort: str, *, model: str, custom_llm_provider: str | None, deployment_model_info: object
|
||||
) -> bool:
|
||||
|
|
|
|||
|
|
@ -92,7 +92,6 @@ _OBJECT_LIST: Final = TypeAdapter(list[object])
|
|||
|
||||
|
||||
_NO_SETTINGS: Final[Mapping[str, object]] = MappingProxyType({})
|
||||
_NO_OPERATOR_MODEL_INFO: Final[Mapping[str, object]] = MappingProxyType({})
|
||||
|
||||
|
||||
def proxy_general_settings() -> Mapping[str, object]:
|
||||
|
|
@ -243,16 +242,14 @@ def _mapped_model_info(model: str, custom_llm_provider: str | None) -> ModelInfo
|
|||
|
||||
|
||||
def model_reasoning_efforts(model: str, custom_llm_provider: str | None) -> tuple[str, ...] | None:
|
||||
from litellm.litellm_core_utils.reasoning_effort_capability import (
|
||||
deployment_is_catalog_mapped,
|
||||
resolve_supported_reasoning_efforts,
|
||||
)
|
||||
from litellm.litellm_core_utils.reasoning_effort_capability import resolve_supported_reasoning_efforts
|
||||
from litellm.utils import has_catalog_entry
|
||||
|
||||
model_info: Final = _mapped_model_info(model, custom_llm_provider)
|
||||
if model_info is None:
|
||||
return None
|
||||
return resolve_supported_reasoning_efforts(
|
||||
model_info, deployment_is_mapped=deployment_is_catalog_mapped(model_info, _NO_OPERATOR_MODEL_INFO)
|
||||
model_info, deployment_is_mapped=has_catalog_entry(model, custom_llm_provider)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -262,11 +259,6 @@ def _lowest_tier_instead_of_off(levels: tuple[str, ...] | None) -> str | None:
|
|||
return next((tier for tier in _LOWEST_REASONING_TIERS if tier in levels), None)
|
||||
|
||||
|
||||
def cost_map_row_lacks_reasoning(model: str, custom_llm_provider: str | None) -> bool:
|
||||
model_info: Final = _mapped_model_info(model, custom_llm_provider)
|
||||
return model_info is not None and not model_info.get("supports_reasoning")
|
||||
|
||||
|
||||
def reasoning_off_level(model: str, custom_llm_provider: str | None, *, drop_params: bool) -> str | None:
|
||||
from litellm.utils import supports_none_reasoning_effort, supports_reasoning
|
||||
|
||||
|
|
|
|||
|
|
@ -405,7 +405,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
return OpenAIGPT5Config.effort_resolves_to_none(model, effort)
|
||||
|
||||
@staticmethod
|
||||
def _supports_reasoning_param(model: str) -> bool:
|
||||
def supports_reasoning_param(model: str) -> bool:
|
||||
from litellm.utils import get_model_info_helper
|
||||
|
||||
try:
|
||||
|
|
@ -503,7 +503,7 @@ class OpenAIResponsesAPIConfig(BaseResponsesAPIConfig):
|
|||
if (
|
||||
self.custom_llm_provider == LlmProviders.OPENAI
|
||||
and self._requests_reasoning_effort(params.get("reasoning"))
|
||||
and not self._supports_reasoning_param(model=model)
|
||||
and not self.supports_reasoning_param(model=model)
|
||||
):
|
||||
if drop_params or litellm.drop_params:
|
||||
dropped_reasoning: Final = params.pop("reasoning", None)
|
||||
|
|
|
|||
|
|
@ -781,3 +781,39 @@ class TestDisabledOnAnOffMapOpenAIDeployment:
|
|||
)
|
||||
|
||||
assert "reasoning" not in sent
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_chat_route_sends_none_when_the_operator_sets_the_deployment_mode(
|
||||
self, respx_mock: respx.MockRouter, local_model_cost_map: None
|
||||
) -> None:
|
||||
sent: Final = await _body_sent_upstream(
|
||||
respx_mock,
|
||||
{**_OFF_MAP_OPENAI_DEPLOYMENT, "model": "openai/vendor/off-map-hybrid-reasoner"},
|
||||
{"mode": "responses"},
|
||||
r"https://compatible\.example\.test/v1/responses",
|
||||
_RESPONSE,
|
||||
thinking={"type": "disabled"},
|
||||
stop_sequences=["STOP"],
|
||||
)
|
||||
|
||||
assert sent["reasoning"]["effort"] == "none"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"messages_params",
|
||||
[{"thinking": {"type": "disabled"}}, {"thinking": {"type": "adaptive"}, "output_config": {"effort": "none"}}],
|
||||
ids=["disabled", "adaptive-effort-none"],
|
||||
)
|
||||
async def test_the_responses_bridge_sends_none_where_its_transformation_accepts_it(
|
||||
self, respx_mock: respx.MockRouter, local_model_cost_map: None, messages_params: Mapping[str, object]
|
||||
) -> None:
|
||||
sent: Final = await _body_sent_upstream(
|
||||
respx_mock,
|
||||
{**_OFF_MAP_OPENAI_DEPLOYMENT, "model": "openai/vendor/off-map-hybrid-reasoner"},
|
||||
{},
|
||||
r"https://compatible\.example\.test/v1/responses",
|
||||
_RESPONSE,
|
||||
**messages_params,
|
||||
)
|
||||
|
||||
assert sent["reasoning"] == {"effort": "none"}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue