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 <cursoragent@cursor.com>
This commit is contained in:
parent
68d67212cd
commit
273855b4e2
@ -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
|
||||
|
||||
@ -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'
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user