diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 9774b762396..b53ec1e52da 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -35,6 +35,17 @@ from ..common_utils import ( if TYPE_CHECKING: from litellm.litellm_core_utils.litellm_logging import Logging as LiteLLMLoggingObj +_STRUCTURED_OUTPUT_FORMAT_TYPES: Final = frozenset({"json_schema", "json_object"}) + + +def _has_structured_output_format(text: object) -> bool: + if not isinstance(text, dict): + return False + format_config: Final = text.get("format") + if not isinstance(format_config, dict): + return False + return format_config.get("type") in _STRUCTURED_OUTPUT_FORMAT_TYPES + class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): def __init__(self) -> None: @@ -106,6 +117,7 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): "reasoning", "previous_response_id", "truncation", + *(("text",) if _has_structured_output_format(request.get("text")) else ()), } return {k: v for k, v in request.items() if k in allowed_keys} diff --git a/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py b/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py index 0b04dd0ed78..06543396678 100644 --- a/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py +++ b/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py @@ -196,6 +196,55 @@ class TestChatGPTResponsesAPITransformation: "function": {"name": "hello"}, } + @pytest.mark.parametrize( + "text_param", + [ + { + "format": { + "type": "json_schema", + "name": "verdict", + "schema": { + "type": "object", + "properties": {"block": {"type": "boolean"}}, + "required": ["block"], + }, + } + }, + {"format": {"type": "json_object"}}, + ], + ) + def test_chatgpt_preserves_structured_output_text(self, text_param): + config = ChatGPTResponsesAPIConfig() + request = config.transform_responses_api_request( + model="chatgpt/gpt-5.3-codex", + input="hi", + response_api_optional_request_params={"text": text_param}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + + assert request["text"] == text_param + + @pytest.mark.parametrize( + "text_param", + [ + {"format": {"type": "text"}}, + {"verbosity": "low"}, + "not-a-dict", + ], + ) + def test_chatgpt_drops_non_structured_text_param(self, text_param): + config = ChatGPTResponsesAPIConfig() + request = config.transform_responses_api_request( + model="chatgpt/gpt-5.3-codex", + input="hi", + response_api_optional_request_params={"text": text_param}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + + assert "text" not in request + @pytest.mark.parametrize( ("model_name", "response_model"), [