mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-29 06:33:28 +00:00
fix(chatgpt): send system input items to Codex as developer (#48668)
* fix(chatgpt): send system input items to Codex as developer
The Codex backend rejects {"type": "message", "role": "system"} input
items with 400 "System messages are not allowed", while it accepts the
same item with role=developer. The chat completions bridge emits that
shape for every non-leading system message, and typed Responses API
clients send it directly, so both paths failed. Rewrite system items to
developer in place, which keeps their position and content.
Fixes #48562
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(chatgpt): annotate system-item rewrite with read-only types
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* fix(chatgpt): rewrite system items on the typed input before the base transform
Keeps the rewrite on known types so basedpyright sees no new unknowns.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
1b932332c1
commit
73cd296418
2 changed files with 38 additions and 1 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
[
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue