diff --git a/litellm/llms/openai/responses/guardrail_translation/handler.py b/litellm/llms/openai/responses/guardrail_translation/handler.py index da0fc62f962..23cfceba306 100644 --- a/litellm/llms/openai/responses/guardrail_translation/handler.py +++ b/litellm/llms/openai/responses/guardrail_translation/handler.py @@ -505,6 +505,15 @@ def _patch_or_convert_request_fields( input_items, converted_instructions = ( LiteLLMResponsesTransformationHandler().convert_chat_completion_messages_to_responses_api(structured_messages) ) + if not input_items and converted_instructions is not None: + # The Responses API rejects an empty input, so a lone system message stays a system item, as the chat + # bridge sends a system-only request. + system_item: Final = { + "type": "message", + "role": "system", + "content": [{"type": "input_text", "text": converted_instructions}], + } + return _RequestFields(input=(system_item,), instructions=None) return _RequestFields(input=tuple(input_items), instructions=converted_instructions) diff --git a/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py b/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py index d3cd2479a46..3e3080298c9 100644 --- a/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py +++ b/tests/unit/llms/openai/responses/test_openai_responses_guardrail_handler.py @@ -3128,6 +3128,27 @@ class TestPatchEdgeBranches: *trailing, ] + @pytest.mark.asyncio + @pytest.mark.parametrize( + "system_content", + ["Answer from the memo only.", [{"type": "input_text", "text": "Answer from the memo only."}]], + ) + async def test_fallback_leaving_only_the_system_message_keeps_it_an_input_item(self, system_content): + """The Responses API rejects an empty input, so a rebuild left holding only the system + message sends it as a system item rather than as instructions over an empty input.""" + handler = OpenAIResponsesHandler() + data = { + "model": "gpt-5.6", + "input": [{"role": "system", "content": system_content}, {"role": "user", "content": "memo " * 400}], + } + + result = await handler.process_input_messages(data, DroppingRewriteGuardrail()) + + assert "instructions" not in result + assert result["input"] == [ + {"type": "message", "role": "system", "content": [{"type": "input_text", "text": "Answer from the memo only."}]} + ] + def test_item_rewrite_field_ignores_non_string_type(self): from litellm.llms.openai.responses.guardrail_translation.handler import _item_rewrite_field