mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-15 09:12:18 +00:00
fix(anthropic): keep sending reasoning_effort none on the chat route to an openai/ deployment the cost map does not list
The router registers an unlisted deployment as a stub row with no mode, which made its reasoning levels read as known-empty, so thinking disabled stopped sending none on the chat-completions route. The levels now read as unknown for such a row. The Responses bridge keeps sending no reasoning there, since the Responses transformation answers 400 to reasoning.effort on a row without supports_reasoning.
This commit is contained in:
parent
9dd5e9a008
commit
927601ac0f
3 changed files with 61 additions and 3 deletions
|
|
@ -52,6 +52,7 @@ 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,
|
||||
|
|
@ -276,7 +277,9 @@ 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)
|
||||
return None if off_level is None else {**reasoning_params, "effort": off_level}
|
||||
if off_level is None or cost_map_row_lacks_reasoning(model, custom_llm_provider):
|
||||
return None
|
||||
return {**reasoning_params, "effort": off_level}
|
||||
if drop_params and effort not in _KNOWN_REASONING_EFFORTS:
|
||||
verbose_logger.warning(
|
||||
"Dropping reasoning.effort=%r for %s, no reasoning API defines it, because drop_params is set",
|
||||
|
|
|
|||
|
|
@ -92,6 +92,7 @@ _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]:
|
||||
|
|
@ -242,12 +243,17 @@ 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 resolve_supported_reasoning_efforts
|
||||
from litellm.litellm_core_utils.reasoning_effort_capability import (
|
||||
deployment_is_catalog_mapped,
|
||||
resolve_supported_reasoning_efforts,
|
||||
)
|
||||
|
||||
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=True)
|
||||
return resolve_supported_reasoning_efforts(
|
||||
model_info, deployment_is_mapped=deployment_is_catalog_mapped(model_info, _NO_OPERATOR_MODEL_INFO)
|
||||
)
|
||||
|
||||
|
||||
def _lowest_tier_instead_of_off(levels: tuple[str, ...] | None) -> str | None:
|
||||
|
|
@ -256,6 +262,11 @@ 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
|
||||
|
||||
|
|
|
|||
|
|
@ -737,3 +737,47 @@ class TestAnOpenAIChatRouteRequestGetsTheResponsesBridgeThinkingControls:
|
|||
)
|
||||
|
||||
assert "reasoning_effort" not in sent
|
||||
|
||||
|
||||
_OFF_MAP_OPENAI_DEPLOYMENT: Final = {
|
||||
"model": "openai/off-map-hybrid-reasoner",
|
||||
"api_base": "https://compatible.example.test/v1",
|
||||
"api_key": "fake-key",
|
||||
}
|
||||
|
||||
|
||||
class TestDisabledOnAnOffMapOpenAIDeployment:
|
||||
@pytest.fixture(autouse=True)
|
||||
def _httpx_transport(self, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
monkeypatch.setattr(litellm, "disable_aiohttp_transport", True)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_chat_route_sends_reasoning_effort_none(
|
||||
self, respx_mock: respx.MockRouter, local_model_cost_map: None
|
||||
) -> None:
|
||||
sent: Final = await _body_sent_upstream(
|
||||
respx_mock,
|
||||
_OFF_MAP_OPENAI_DEPLOYMENT,
|
||||
{},
|
||||
r"https://compatible\.example\.test/v1/chat/completions",
|
||||
_CHAT_COMPLETION,
|
||||
thinking={"type": "disabled"},
|
||||
stop_sequences=["STOP"],
|
||||
)
|
||||
|
||||
assert sent["reasoning_effort"] == "none"
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_the_responses_bridge_sends_no_reasoning_its_transformation_would_refuse(
|
||||
self, respx_mock: respx.MockRouter, local_model_cost_map: None
|
||||
) -> None:
|
||||
sent: Final = await _body_sent_upstream(
|
||||
respx_mock,
|
||||
_OFF_MAP_OPENAI_DEPLOYMENT,
|
||||
{},
|
||||
r"https://compatible\.example\.test/v1/responses",
|
||||
_RESPONSE,
|
||||
thinking={"type": "disabled"},
|
||||
)
|
||||
|
||||
assert "reasoning" not in sent
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue