diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 2352de35b5b..040b7b8c1d8 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -2,6 +2,7 @@ from collections.abc import Mapping from typing import TYPE_CHECKING, Any, Final, cast import httpx +from openai.types.responses.response_input_param import ResponseInputItemParam from litellm.exceptions import AuthenticationError from litellm.litellm_core_utils.core_helpers import process_response_headers @@ -42,6 +43,16 @@ if TYPE_CHECKING: _CHATGPT_SERVICE_TIERS: Final = {"default": "default", "priority": "priority", "fast": "priority"} +def _system_item_as_developer(item: ResponseInputItemParam) -> ResponseInputItemParam: + # The Codex backend 400s on {"type": "message", "role": "system"} items ("System messages are not + # allowed") but accepts role=developer in the same position, which is what the Codex CLI sends. + if item.get("role") != "system": + return item + return cast( # cast-ok: only the role changes, and developer is valid wherever system is + ResponseInputItemParam, {**item, "role": "developer"} + ) + + class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): def __init__(self, authenticator: Authenticator | None = None) -> None: super().__init__() @@ -101,7 +112,9 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): ) -> dict: request: Final = super().transform_responses_api_request( model, - [{"role": "user", "content": input}] if isinstance(input, str) else input, + [{"role": "user", "content": input}] + if isinstance(input, str) + else [_system_item_as_developer(item) for item in input], response_api_optional_request_params, litellm_params, headers, 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 d88bdfc0b41..8985d0b7fd8 100644 --- a/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py +++ b/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py @@ -74,6 +74,30 @@ class TestChatGPTResponsesAPITransformation: assert "service_tier" not in request + def test_chatgpt_sends_system_input_items_as_developer(self) -> None: + system_string_item: Final = {"role": "system", "content": "Be brief."} + system_blocks_item: Final = { + "type": "message", + "role": "system", + "content": [{"type": "input_text", "text": "Answer in French."}], + } + user_item: Final = {"role": "user", "content": "Hello"} + developer_item: Final = {"role": "developer", "content": "Use metric units."} + request: Final = ChatGPTResponsesAPIConfig().transform_responses_api_request( + model="chatgpt/gpt-6.1-sol", + input=[system_string_item, user_item, system_blocks_item, developer_item], + response_api_optional_request_params={}, + litellm_params=GenericLiteLLMParams(), + headers={}, + ) + + assert request["input"] == [ + {**system_string_item, "role": "developer"}, + user_item, + {**system_blocks_item, "role": "developer"}, + developer_item, + ] + @pytest.mark.parametrize( "model_name", [