This commit is contained in:
Alexandr A. Alexandrov 2026-11-07 03:15:25 +00:00 • committed by GitHub
commit 8efe952649
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 94 additions and 0 deletions

View file

@ -1,3 +1,4 @@
import hashlib
from collections.abc import Mapping
from typing import TYPE_CHECKING, Any, Final, cast
@ -130,6 +131,7 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig):
if "reasoning.encrypted_content" not in include:
include.append("reasoning.encrypted_content")
request["include"] = include
request["input"] = self._normalize_responses_input_for_chatgpt(request.get("input"))
allowed_keys: Final = {
"model",
@ -153,6 +155,36 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig):
filtered["service_tier"] = service_tier
return filtered
@classmethod
def _normalize_responses_input_for_chatgpt(cls, input_value: Any) -> Any:
if not isinstance(input_value, list):
return input_value
normalized_input: list[Any] = []
changed = False
for item in input_value:
if isinstance(item, dict):
item_type = item.get("type")
if item_type in {"reasoning", "web_search_call"}:
changed = True
continue
if item_type == "function_call":
current_id = item.get("id")
normalized_id = cls._normalize_function_call_item_id(current_id)
if normalized_id != current_id:
item = dict(item)
item["id"] = normalized_id
changed = True
normalized_input.append(item)
return normalized_input if changed else input_value
@staticmethod
def _normalize_function_call_item_id(item_id: Any) -> Any:
if not isinstance(item_id, str) or not item_id or item_id.startswith("fc"):
return item_id
digest: Final = hashlib.sha256(item_id.encode("utf-8")).hexdigest()[:32]
return f"fc_{digest}"
def transform_response_api_response(
self,
model: str,

View file

@ -218,6 +218,68 @@ class TestChatGPTResponsesAPITransformation:
assert "reasoning.encrypted_content" in request["include"]
assert request["instructions"].startswith("You are Codex, based on GPT-5.")
def test_chatgpt_normalizes_foreign_function_call_item_id(self):
config = ChatGPTResponsesAPIConfig()
input_items = [
{
"type": "function_call",
"id": "toolu_fixture_123",
"call_id": "call_fixture_123",
"name": "read_file",
"arguments": '{"path":"README.md"}',
}
]
request = config.transform_responses_api_request(
model="chatgpt/gpt-5.6-sol",
input=input_items,
response_api_optional_request_params={},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert request["input"] == [
{
"type": "function_call",
"id": "fc_064009edc4c975f0fa4b447f8ea388e0",
"call_id": "call_fixture_123",
"name": "read_file",
"arguments": '{"path":"README.md"}',
}
]
def test_chatgpt_omits_replayed_private_output_items(self):
config = ChatGPTResponsesAPIConfig()
input_items = [
{"type": "reasoning", "id": "rs_fixture", "encrypted_content": "opaque"},
{
"type": "message",
"role": "user",
"content": [{"type": "input_text", "text": "continue"}],
},
{"type": "web_search_call", "id": "ws_fixture", "status": "completed"},
{
"type": "function_call",
"id": "fc_fixture",
"call_id": "call_fixture",
"name": "read_file",
"arguments": '{"path":"README.md"}',
},
]
request = config.transform_responses_api_request(
model="chatgpt/gpt-5.6-sol",
input=input_items,
response_api_optional_request_params={},
litellm_params=GenericLiteLLMParams(),
headers={},
)
assert [item["type"] for item in request["input"]] == [
"message",
"function_call",
]
@pytest.mark.parametrize(
"model_name",
[