diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 8e4bbf1d3c9..9e12738b903 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -28,6 +28,17 @@ from ..common_utils import ( get_chatgpt_default_instructions, ) +_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: @@ -99,6 +110,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/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py b/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py index 8e0415d50de..e7f0937caf1 100644 --- a/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py +++ b/tests/test_litellm/llms/chatgpt/responses/test_chatgpt_responses_transformation.py @@ -155,6 +155,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"), [