diff --git a/litellm/llms/openai/chat/gpt_5_transformation.py b/litellm/llms/openai/chat/gpt_5_transformation.py index eb7bb384a7..a665ef65e5 100644 --- a/litellm/llms/openai/chat/gpt_5_transformation.py +++ b/litellm/llms/openai/chat/gpt_5_transformation.py @@ -212,6 +212,12 @@ class OpenAIGPT5Config(OpenAIGPTConfig): model: str, drop_params: bool, ) -> dict: + # AI SDK / Responses-style aliases; never valid on Chat Completions when not + # bridged to Responses API (see main.responses_api_bridge_check). + self._strip_reasoning_summary_aliases_for_chat_completions( + non_default_params, optional_params + ) + if self.is_model_gpt_5_search_model(model): if "max_tokens" in non_default_params: optional_params["max_completion_tokens"] = non_default_params.pop( @@ -224,12 +230,6 @@ class OpenAIGPT5Config(OpenAIGPTConfig): drop_params=drop_params, ) - # AI SDK / Responses-style aliases; never valid on Chat Completions when not - # bridged to Responses API (see main.responses_api_bridge_check). - self._strip_reasoning_summary_aliases_for_chat_completions( - non_default_params, optional_params - ) - # Get raw reasoning_effort and effective effort level for all guards. # Use effective_effort (extracted string) for xhigh validation, "none" checks, and # tool/sampling guards — dict inputs like {"effort": "none", "summary": "detailed"} diff --git a/litellm/utils.py b/litellm/utils.py index d473446b18..f61a47c401 100644 --- a/litellm/utils.py +++ b/litellm/utils.py @@ -9494,14 +9494,17 @@ def get_non_default_completion_params(kwargs: dict) -> dict: def peek_reasoning_summary_aliases(optional_params: dict) -> Optional[Any]: """Read AI-SDK-style reasoning summary from optional_params or nested extra_body.""" - rs = optional_params.get("reasoningSummary") or optional_params.get( - "reasoning_summary" - ) + rs = optional_params.get("reasoningSummary") + if rs is None: + rs = optional_params.get("reasoning_summary") if rs is not None: return rs extra_body = optional_params.get("extra_body") if isinstance(extra_body, dict): - return extra_body.get("reasoningSummary") or extra_body.get("reasoning_summary") + rs = extra_body.get("reasoningSummary") + if rs is None: + rs = extra_body.get("reasoning_summary") + return rs return None @@ -9511,18 +9514,18 @@ def strip_reasoning_summary_aliases_from_optional_params( """Copy optional_params; remove reasoningSummary aliases from top-level and extra_body.""" op = dict(optional_params) rs_val = op.pop("reasoningSummary", None) + snake_rs_val = op.pop("reasoning_summary", None) if rs_val is None: - rs_val = op.pop("reasoning_summary", None) + rs_val = snake_rs_val eb = op.get("extra_body") if isinstance(eb, dict): eb = dict(eb) + eb_rs_val = eb.pop("reasoningSummary", None) + eb_snake_rs_val = eb.pop("reasoning_summary", None) if rs_val is None: - rs_val = eb.pop("reasoningSummary", None) or eb.pop( - "reasoning_summary", None - ) - else: - eb.pop("reasoningSummary", None) - eb.pop("reasoning_summary", None) + rs_val = eb_rs_val + if rs_val is None: + rs_val = eb_snake_rs_val if eb: op["extra_body"] = eb else: diff --git a/tests/test_litellm/llms/openai/test_gpt5_transformation.py b/tests/test_litellm/llms/openai/test_gpt5_transformation.py index ebf7681f2f..be079ee708 100644 --- a/tests/test_litellm/llms/openai/test_gpt5_transformation.py +++ b/tests/test_litellm/llms/openai/test_gpt5_transformation.py @@ -4,7 +4,11 @@ import litellm from litellm.litellm_core_utils.get_model_cost_map import get_model_cost_map from litellm.llms.openai.chat.gpt_5_transformation import OpenAIGPT5Config from litellm.llms.openai.openai import OpenAIConfig -from litellm.utils import _is_explicitly_disabled_factory +from litellm.utils import ( + _is_explicitly_disabled_factory, + peek_reasoning_summary_aliases, + strip_reasoning_summary_aliases_from_optional_params, +) @pytest.fixture() @@ -1007,6 +1011,74 @@ def test_gpt5_search_drops_unsupported_params(config: OpenAIConfig): assert "tools" not in params +def test_gpt5_search_strips_reasoning_summary_aliases(gpt5_config: OpenAIGPT5Config): + """Search models still strip Responses-only reasoning summary aliases.""" + non_default_params = { + "reasoningSummary": "auto", + "reasoning_summary": "ignored", + } + optional_params = { + "extra_body": { + "reasoningSummary": "auto", + "reasoning_summary": "ignored", + "metadata": "ok", + } + } + + params = gpt5_config.map_openai_params( + non_default_params=non_default_params, + optional_params=optional_params, + model="gpt-5-search-api", + drop_params=False, + ) + + assert "reasoningSummary" not in non_default_params + assert "reasoning_summary" not in non_default_params + assert "reasoningSummary" not in params + assert "reasoning_summary" not in params + assert params["extra_body"] == {"metadata": "ok"} + + +def test_reasoning_summary_alias_helpers_preserve_falsy_and_strip_all_aliases(): + optional_params = {"reasoningSummary": False, "reasoning_summary": "ignored"} + + assert peek_reasoning_summary_aliases(optional_params) is False + stripped, rs_val = strip_reasoning_summary_aliases_from_optional_params( + optional_params + ) + + assert rs_val is False + assert stripped == {} + + optional_params = { + "extra_body": {"reasoningSummary": False, "reasoning_summary": "ignored"} + } + + assert peek_reasoning_summary_aliases(optional_params) is False + stripped, rs_val = strip_reasoning_summary_aliases_from_optional_params( + optional_params + ) + + assert rs_val is False + assert stripped == {} + + optional_params = { + "extra_body": { + "reasoningSummary": "auto", + "reasoning_summary": "ignored", + "metadata": "ok", + } + } + + assert peek_reasoning_summary_aliases(optional_params) == "auto" + stripped, rs_val = strip_reasoning_summary_aliases_from_optional_params( + optional_params + ) + + assert rs_val == "auto" + assert stripped == {"extra_body": {"metadata": "ok"}} + + # GPT-5 unsupported params audit (validated via direct API calls) def test_gpt5_rejects_params_unsupported_by_openai(config: OpenAIConfig): """Params that OpenAI rejects for all GPT-5 reasoning models."""