This commit is contained in:
Wei Tsai 2026-11-05 05:39:11 +00:00 • committed by GitHub
commit bdce40263c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 89 additions and 0 deletions

View file

@ -151,6 +151,36 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig):
filtered["service_tier"] = service_tier
return filtered
def sign_request(
self,
headers: dict, # mutable-ok: mirrors the base hook signature
optional_params: dict, # mutable-ok: mirrors the base hook signature
request_data: dict, # mutable-ok: the hook contract allows in-place body fixes
api_base: str,
api_key: str | None = None,
model: str | None = None,
stream: bool | None = None,
fake_stream: bool | None = None,
) -> tuple[dict, bytes | None]: # mutable-ok: mirrors the base hook signature
# extra_body is merged into the payload after transform_responses_api_request
# (llm_http_handler does data.update(transform_extra_body(...)) after the
# transform), so a string input carried in extra_body re-introduces the
# shorthand the entry wrap removed and the backend 400s again. sign_request
# runs after the body is final, so re-apply the entry wrap's list form here.
request_input: Final[object] = request_data.get("input")
if isinstance(request_input, str):
request_data["input"] = [{"role": "user", "content": request_input}] # rebind-ok: in-place final-body fix
return super().sign_request(
headers=headers,
optional_params=optional_params,
request_data=request_data,
api_base=api_base,
api_key=api_key,
model=model,
stream=stream,
fake_stream=fake_stream,
)
def transform_response_api_response(
self,
model: str,

View file

@ -128,6 +128,65 @@ class TestChatGPTResponsesAPITransformation:
assert isinstance(config, ChatGPTResponsesAPIConfig)
assert config.custom_llm_provider == LlmProviders.CHATGPT
def test_sign_request_rewraps_string_input_reintroduced_after_transform(self):
"""extra_body merges after transform_responses_api_request, so a string
input merged there re-introduces the shorthand the entry wrap removed."""
config: Final = ChatGPTResponsesAPIConfig()
request: Final = config.transform_responses_api_request(
model="chatgpt/gpt-6.1-sol",
input="Reply with exactly the word: pong",
response_api_optional_request_params={},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert request["input"] == [{"role": "user", "content": "Reply with exactly the word: pong"}]
# llm_http_handler merges extra_body after the transform:
# data.update(config.transform_extra_body(extra_body=..., request=data, ...))
request.update({"input": "Reply with exactly the word: pong"})
config.sign_request(
headers={},
optional_params={},
request_data=request,
api_base="https://chatgpt.com/backend-api/codex/responses",
api_key=None,
model="chatgpt/gpt-6.1-sol",
)
assert request["input"] == [{"role": "user", "content": "Reply with exactly the word: pong"}]
def test_sign_request_wraps_string_input_without_entry_wrap(self):
"""The final signing hook also covers payload paths that never ran the
entry wrap (list input fully replaced by a string via extra_body)."""
config: Final = ChatGPTResponsesAPIConfig()
request: Final = {
"model": "chatgpt/gpt-6.1-sol",
"input": "standalone shorthand",
}
config.sign_request(
headers={},
optional_params={},
request_data=request,
api_base="https://chatgpt.com/backend-api/codex/responses",
api_key=None,
model="chatgpt/gpt-6.1-sol",
)
assert request["input"] == [{"role": "user", "content": "standalone shorthand"}]
def test_sign_request_leaves_list_input_unchanged(self):
config: Final = ChatGPTResponsesAPIConfig()
list_input: Final = [{"role": "user", "content": [{"type": "input_text", "text": "hi"}]}]
request: Final = {"model": "chatgpt/gpt-6.1-sol", "input": list_input}
config.sign_request(
headers={},
optional_params={},
request_data=request,
api_base="https://chatgpt.com/backend-api/codex/responses",
api_key=None,
model="chatgpt/gpt-6.1-sol",
)
assert request["input"] == list_input
@pytest.mark.parametrize(
"model_name",
[