diff --git a/litellm/proxy/auth/auth_object_prefetch.py b/litellm/proxy/auth/auth_object_prefetch.py index fd5b99951f4..fd0873cb51e 100644 --- a/litellm/proxy/auth/auth_object_prefetch.py +++ b/litellm/proxy/auth/auth_object_prefetch.py @@ -16,7 +16,7 @@ from litellm._internal_context import service_target from litellm._logging import verbose_proxy_logger from litellm.caching.redis_batch import active_request_redis_batch from litellm.caching.redis_cache import RedisCache -from litellm.constants import DEFAULT_IN_MEMORY_TTL, REGISTRY_ERROR_NEGATIVE_CACHE_TTL +from litellm.constants import REGISTRY_ERROR_NEGATIVE_CACHE_TTL from litellm.models.organization import LiteLLM_OrganizationTable from litellm.models.team import LiteLLM_TeamTableCachedObj from litellm.models.team_membership import LiteLLM_TeamMembership @@ -194,14 +194,17 @@ def _iter_entries(refs: AuthObjectRefs, management_ttl: float) -> Iterator[_Cach None, ) if refs.organization_id is not None: + # Organization entries use the management TTL like every other prefetched object: a + # 5s fuse expires before slow requests reach the getters, sending them back to + # the DB the prefetch was meant to spare. yield _CacheEntry( - f"org_id:{refs.organization_id}", "organization_row", LiteLLM_OrganizationTable, DEFAULT_IN_MEMORY_TTL + f"org_id:{refs.organization_id}", "organization_row", LiteLLM_OrganizationTable, management_ttl ) yield _CacheEntry( f"org_id:{refs.organization_id}:with_budget", "organization_row", LiteLLM_OrganizationTable, - DEFAULT_IN_MEMORY_TTL, + management_ttl, ) if refs.project_id is not None: yield _CacheEntry(f"project_id:{refs.project_id}", "project_row", LiteLLM_ProjectTableCachedObj, management_ttl) diff --git a/tests/unit/llms/openai/responses/test_reasoning_effort_capability.py b/tests/unit/llms/openai/responses/test_reasoning_effort_capability.py new file mode 100644 index 00000000000..585a0ea3b5f --- /dev/null +++ b/tests/unit/llms/openai/responses/test_reasoning_effort_capability.py @@ -0,0 +1,86 @@ +import re + +import pytest + +import litellm +from litellm.llms.azure.responses.transformation import AzureOpenAIResponsesAPIConfig +from litellm.llms.openai.responses.transformation import OpenAIResponsesAPIConfig + + +@pytest.mark.parametrize("effort", ["minimal", "low"]) +def test_rejects_explicitly_unsupported_lower_reasoning_effort(effort: str) -> None: + config = OpenAIResponsesAPIConfig() + + with pytest.raises(litellm.UnsupportedParamsError, match=f"reasoning.effort={effort}"): + config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": effort}}, + model="gpt-5.5-pro", + drop_params=False, + ) + + +def test_keeps_supported_reasoning_effort() -> None: + config = OpenAIResponsesAPIConfig() + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "medium"}}, + model="gpt-5.5-pro", + drop_params=False, + ) + + assert result["reasoning"] == {"effort": "medium"} + + +def test_drop_params_removes_only_unsupported_effort() -> None: + config = OpenAIResponsesAPIConfig() + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal", "summary": "detailed"}}, + model="gpt-5.5-pro", + drop_params=True, + ) + + assert result["reasoning"] == {"summary": "detailed"} + + +def test_drop_params_revalidates_temperature_after_effort_drop() -> None: + """Dropping an unsupported effort revalidates temperature against the emptied effort. + + `gpt-5.1` really declares `xhigh` unsupported while supporting (and defaulting to) + `none`, so no capability stubbing is needed: the dropped effort resolves to `none` + and a valid non-default temperature survives. + """ + config = OpenAIResponsesAPIConfig() + + result = config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "xhigh"}, "temperature": 0.5}, + model="gpt-5.1", + drop_params=True, + ) + + assert "reasoning" not in result + assert result["temperature"] == 0.5 + + +def test_azure_config_uses_azure_capability_map_for_effort() -> None: + """A bare Azure deployment name must resolve against the Azure map entry. + + OpenAI's `gpt-5.4` entry explicitly disables `minimal` while Azure's enables it; + the OpenAI lookup would wrongly reject an effort Azure supports. + """ + openai_config = OpenAIResponsesAPIConfig() + with pytest.raises(litellm.UnsupportedParamsError, match=re.escape("reasoning.effort=minimal")): + openai_config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal"}}, + model="gpt-5.4", + drop_params=False, + ) + + azure_config = AzureOpenAIResponsesAPIConfig() + result = azure_config.map_openai_params( + response_api_optional_params={"reasoning": {"effort": "minimal"}}, + model="gpt-5.4", + drop_params=False, + ) + + assert result["reasoning"] == {"effort": "minimal"} diff --git a/tests/unit/proxy/auth/test_auth_object_prefetch.py b/tests/unit/proxy/auth/test_auth_object_prefetch.py index ffac95d6815..ff5f42587aa 100644 --- a/tests/unit/proxy/auth/test_auth_object_prefetch.py +++ b/tests/unit/proxy/auth/test_auth_object_prefetch.py @@ -186,8 +186,8 @@ async def test_cold_regime_is_one_mget_one_query_and_the_getters_never_touch_io_ assert sets == sorted( [ f"SET {TEAM_ID}_{USER_ID} ttl=5", - f"SET org_id:{ORG_ID} ttl=5", - f"SET org_id:{ORG_ID}:with_budget ttl=5", + f"SET org_id:{ORG_ID} ttl=60", + f"SET org_id:{ORG_ID}:with_budget ttl=60", f"SET {USER_ID} ttl=60", f"SET team_id:{TEAM_ID} ttl=60", f"SET team_membership:{USER_ID}:{TEAM_ID} ttl=None",