diff --git a/litellm/llms/chatgpt/responses/transformation.py b/litellm/llms/chatgpt/responses/transformation.py index 6a724065e8b..8a7d377602a 100644 --- a/litellm/llms/chatgpt/responses/transformation.py +++ b/litellm/llms/chatgpt/responses/transformation.py @@ -98,7 +98,7 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): if "reasoning.encrypted_content" not in include: include.append("reasoning.encrypted_content") request["include"] = include - request["input"] = self._normalize_function_call_item_ids(request.get("input")) + request["input"] = self._normalize_responses_input_for_chatgpt(request.get("input")) allowed_keys: Final = { "model", @@ -121,20 +121,25 @@ class ChatGPTResponsesAPIConfig(OpenAIResponsesAPIConfig): return filtered @classmethod - def _normalize_function_call_item_ids(cls, input_value: Any) -> Any: + 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) and item.get("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 + 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 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 c929c80b747..2ce568a4e46 100644 --- a/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py +++ b/tests/unit/llms/chatgpt/responses/test_chatgpt_responses_transformation.py @@ -219,6 +219,38 @@ class TestChatGPTResponsesAPITransformation: } ] + 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", [