From 273855b4e2e8b3d2cbc61216729e52881ebd0816 Mon Sep 17 00:00:00 2001 From: milan-berri Date: Sun, 7 Jun 2026 02:11:54 +0300 Subject: [PATCH] fix(responses-bridge): map system-only chat request to system input item (#29817) System-only chat requests mapped the system message to instructions and left input=[], which OpenAI's Responses API rejects (it also rejects input=""). When no other messages are present, carry the system message as a role:"system" input item (single copy, correct role) instead of leaving input empty. Mirrors the existing handling of non-string system content. Fixes Open WebUI new-conversation failures on mode:responses Codex models. Co-authored-by: Cursor --- .../transformation.py | 14 ++++++++ ...responses_transformation_transformation.py | 36 +++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/litellm/completion_extras/litellm_responses_transformation/transformation.py b/litellm/completion_extras/litellm_responses_transformation/transformation.py index 51abbbf729..6d8b5cf8a5 100644 --- a/litellm/completion_extras/litellm_responses_transformation/transformation.py +++ b/litellm/completion_extras/litellm_responses_transformation/transformation.py @@ -402,6 +402,20 @@ class LiteLLMResponsesTransformationHandler(CompletionTransformationBridge): instructions, ) = self.convert_chat_completion_messages_to_responses_api(messages) + # OpenAI's Responses API rejects an empty input. For a system-only + # request, carry the system message as a system-role input item instead + # of instructions, mirroring how non-string system content is already + # handled in convert_chat_completion_messages_to_responses_api. + if not input_items and instructions is not None: + input_items = [ + { + "type": "message", + "role": "system", + "content": [{"type": "input_text", "text": instructions}], + } + ] + instructions = None + optional_params = self._extract_extra_body_params(optional_params) # Build responses API request using the reverse transformation logic diff --git a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py index d335c359aa..06457dfebf 100644 --- a/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py +++ b/tests/test_litellm/completion_extras/litellm_responses_transformation/test_completion_extras_litellm_responses_transformation_transformation.py @@ -13,6 +13,9 @@ sys.path.insert( 0, os.path.abspath("../../..") ) # Adds the parent directory to the system-path import litellm +from litellm.completion_extras.litellm_responses_transformation.transformation import ( + LiteLLMResponsesTransformationHandler, +) def test_convert_chat_completion_messages_to_responses_api_image_input(): @@ -860,6 +863,39 @@ def test_extract_extra_body_params_reasoning_effort_override(): assert "extra_body" not in result +def test_transform_request_system_only_message_maps_to_system_input_item(): + """System-only requests must not send input=[] to the Responses API. + + OpenAI rejects both input=[] and input="". When the only message is a + system message, carry it as a system-role input item (single copy, correct + role) rather than leaving input empty or duplicating it into instructions. + """ + handler = LiteLLMResponsesTransformationHandler() + logging_obj = Mock() + messages = [{"role": "system", "content": "You are a helpful assistant."}] + + result = handler.transform_request( + model="gpt-5.3-codex", + messages=messages, + optional_params={}, + litellm_params={}, + headers={}, + litellm_logging_obj=logging_obj, + ) + + assert result["input"] == [ + { + "type": "message", + "role": "system", + "content": [ + {"type": "input_text", "text": "You are a helpful assistant."} + ], + } + ] + # System content lives in input only; not duplicated into instructions. + assert not result.get("instructions") + + def test_transform_request_single_char_keys_not_matched(): """Test that single-character keys are not incorrectly matched to 'metadata' or 'previous_response_id'