From cfde2e4160a1ce02c62e06e0595d1186544048be Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 14:55:33 -0700 Subject: [PATCH 01/11] rename test config --- .circleci/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7158087445..8a02a41633 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1357,7 +1357,7 @@ jobs: # Store test results - store_test_results: path: test-results - e2e_openai_misc_endpoints: + e2e_openai_endpoints: machine: image: ubuntu-2204:2023.10.1 resource_class: xlarge @@ -2429,7 +2429,7 @@ workflows: only: - main - /litellm_.*/ - - e2e_openai_misc_endpoints: + - e2e_openai_endpoints: filters: branches: only: @@ -2571,7 +2571,7 @@ workflows: requires: - local_testing - build_and_test - - e2e_openai_misc_endpoints + - e2e_openai_endpoints - load_testing - test_bad_database_url - llm_translation_testing From 1363f106879c9183ed6cbbc1be1c15417d321bda Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:13:07 -0700 Subject: [PATCH 02/11] fix auth add responses API to llm routes --- litellm/proxy/_types.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index a9fe6517ea..a0893f4938 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -248,6 +248,13 @@ class LiteLLMRoutes(enum.Enum): "/v1/realtime", "/realtime?{model}", "/v1/realtime?{model}", + # responses API + "/responses", + "/v1/responses", + "/responses/{response_id}", + "/v1/responses/{response_id}", + "/responses/{response_id}/input_items", + "/v1/responses/{response_id}/input_items", ] mapped_pass_through_routes = [ From 4510d78cddea8fe96213bd1117904b394d5bea9b Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:13:48 -0700 Subject: [PATCH 03/11] rename folder to test openai endpoints --- .circleci/config.yml | 2 +- .../input.jsonl | 0 .../input_azure.jsonl | 0 .../openai_batch_completions.jsonl | 0 .../openai_fine_tuning.jsonl | 0 .../out.jsonl | 0 .../out_azure.jsonl | 0 .../test_openai_batches_endpoint.py | 0 .../test_openai_files_endpoints.py | 0 .../test_openai_fine_tuning.py | 0 .../test_openai_responses_api.py | 70 +++++++++++++++++++ 11 files changed, 71 insertions(+), 1 deletion(-) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/input.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/input_azure.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/openai_batch_completions.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/openai_fine_tuning.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/out.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/out_azure.jsonl (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/test_openai_batches_endpoint.py (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/test_openai_files_endpoints.py (100%) rename tests/{openai_misc_endpoints_tests => openai_endpoints_tests}/test_openai_fine_tuning.py (100%) create mode 100644 tests/openai_endpoints_tests/test_openai_responses_api.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a02a41633..9f412cbc29 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1474,7 +1474,7 @@ jobs: command: | pwd ls - python -m pytest -s -vv tests/openai_misc_endpoints_tests --junitxml=test-results/junit.xml --durations=5 + python -m pytest -s -vv tests/openai_endpoints_tests --junitxml=test-results/junit.xml --durations=5 no_output_timeout: 120m # Store test results diff --git a/tests/openai_misc_endpoints_tests/input.jsonl b/tests/openai_endpoints_tests/input.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/input.jsonl rename to tests/openai_endpoints_tests/input.jsonl diff --git a/tests/openai_misc_endpoints_tests/input_azure.jsonl b/tests/openai_endpoints_tests/input_azure.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/input_azure.jsonl rename to tests/openai_endpoints_tests/input_azure.jsonl diff --git a/tests/openai_misc_endpoints_tests/openai_batch_completions.jsonl b/tests/openai_endpoints_tests/openai_batch_completions.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/openai_batch_completions.jsonl rename to tests/openai_endpoints_tests/openai_batch_completions.jsonl diff --git a/tests/openai_misc_endpoints_tests/openai_fine_tuning.jsonl b/tests/openai_endpoints_tests/openai_fine_tuning.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/openai_fine_tuning.jsonl rename to tests/openai_endpoints_tests/openai_fine_tuning.jsonl diff --git a/tests/openai_misc_endpoints_tests/out.jsonl b/tests/openai_endpoints_tests/out.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/out.jsonl rename to tests/openai_endpoints_tests/out.jsonl diff --git a/tests/openai_misc_endpoints_tests/out_azure.jsonl b/tests/openai_endpoints_tests/out_azure.jsonl similarity index 100% rename from tests/openai_misc_endpoints_tests/out_azure.jsonl rename to tests/openai_endpoints_tests/out_azure.jsonl diff --git a/tests/openai_misc_endpoints_tests/test_openai_batches_endpoint.py b/tests/openai_endpoints_tests/test_openai_batches_endpoint.py similarity index 100% rename from tests/openai_misc_endpoints_tests/test_openai_batches_endpoint.py rename to tests/openai_endpoints_tests/test_openai_batches_endpoint.py diff --git a/tests/openai_misc_endpoints_tests/test_openai_files_endpoints.py b/tests/openai_endpoints_tests/test_openai_files_endpoints.py similarity index 100% rename from tests/openai_misc_endpoints_tests/test_openai_files_endpoints.py rename to tests/openai_endpoints_tests/test_openai_files_endpoints.py diff --git a/tests/openai_misc_endpoints_tests/test_openai_fine_tuning.py b/tests/openai_endpoints_tests/test_openai_fine_tuning.py similarity index 100% rename from tests/openai_misc_endpoints_tests/test_openai_fine_tuning.py rename to tests/openai_endpoints_tests/test_openai_fine_tuning.py diff --git a/tests/openai_endpoints_tests/test_openai_responses_api.py b/tests/openai_endpoints_tests/test_openai_responses_api.py new file mode 100644 index 0000000000..4b628ae395 --- /dev/null +++ b/tests/openai_endpoints_tests/test_openai_responses_api.py @@ -0,0 +1,70 @@ +import httpx +from openai import OpenAI, BadRequestError +import pytest + + +def generate_key(): + """Generate a key for testing""" + url = "http://0.0.0.0:4000/key/generate" + headers = { + "Authorization": "Bearer sk-1234", + "Content-Type": "application/json", + } + data = {} + + response = httpx.post(url, headers=headers, json=data) + if response.status_code != 200: + raise Exception(f"Key generation failed with status: {response.status_code}") + return response.json()["key"] + + +def get_test_client(): + """Create OpenAI client with generated key""" + key = generate_key() + return OpenAI(api_key=key, base_url="http://0.0.0.0:4000") + + +def validate_response(response): + """ + Validate basic response structure from OpenAI responses API + """ + assert response is not None + assert isinstance(response.choices[0].message.content, str) + assert len(response.choices) > 0 + + +def validate_stream_chunk(chunk): + """ + Validate streaming chunk structure from OpenAI responses API + """ + assert chunk is not None + assert isinstance(chunk.choices[0].delta.content, str) + + +def test_basic_response(): + client = get_test_client() + response = client.responses.create( + model="gpt-4", input="Tell me a three sentence bedtime story about a unicorn." + ) + validate_response(response) + + +def test_streaming_response(): + client = get_test_client() + stream = client.responses.create( + model="gpt-4", input="Tell me a story", stream=True + ) + + collected_chunks = [] + for chunk in stream: + validate_stream_chunk(chunk) + collected_chunks.append(chunk) + + assert len(collected_chunks) > 0 + + +def test_bad_request_error(): + client = get_test_client() + with pytest.raises(BadRequestError): + # Trigger error with invalid model name + client.responses.create(model="non-existent-model", input="This should fail") From c0dbb0946fdd01a515e82bb451706210f29f8436 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:17:47 -0700 Subject: [PATCH 04/11] working e2e tests for responses api --- tests/openai_endpoints_tests/test_openai_responses_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/openai_endpoints_tests/test_openai_responses_api.py b/tests/openai_endpoints_tests/test_openai_responses_api.py index 4b628ae395..473d49bd7c 100644 --- a/tests/openai_endpoints_tests/test_openai_responses_api.py +++ b/tests/openai_endpoints_tests/test_openai_responses_api.py @@ -44,20 +44,20 @@ def validate_stream_chunk(chunk): def test_basic_response(): client = get_test_client() response = client.responses.create( - model="gpt-4", input="Tell me a three sentence bedtime story about a unicorn." + model="gpt-4o", input="just respond with the word 'ping'" ) - validate_response(response) + print("basic response=", response) def test_streaming_response(): client = get_test_client() stream = client.responses.create( - model="gpt-4", input="Tell me a story", stream=True + model="gpt-4o", input="just respond with the word 'ping'", stream=True ) collected_chunks = [] for chunk in stream: - validate_stream_chunk(chunk) + print("stream chunk=", chunk) collected_chunks.append(chunk) assert len(collected_chunks) > 0 From efab7d2645a1b06c99e98c9252f5f6154481ece9 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:25:50 -0700 Subject: [PATCH 05/11] add basic validation tests for e2e responses create endpoint --- .../test_openai_responses_api.py | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/tests/openai_endpoints_tests/test_openai_responses_api.py b/tests/openai_endpoints_tests/test_openai_responses_api.py index 473d49bd7c..f853d53532 100644 --- a/tests/openai_endpoints_tests/test_openai_responses_api.py +++ b/tests/openai_endpoints_tests/test_openai_responses_api.py @@ -29,8 +29,21 @@ def validate_response(response): Validate basic response structure from OpenAI responses API """ assert response is not None - assert isinstance(response.choices[0].message.content, str) + assert hasattr(response, "choices") assert len(response.choices) > 0 + assert hasattr(response.choices[0], "message") + assert hasattr(response.choices[0].message, "content") + assert isinstance(response.choices[0].message.content, str) + assert hasattr(response, "id") + assert isinstance(response.id, str) + assert hasattr(response, "model") + assert isinstance(response.model, str) + assert hasattr(response, "created") + assert isinstance(response.created, int) + assert hasattr(response, "usage") + assert hasattr(response.usage, "prompt_tokens") + assert hasattr(response.usage, "completion_tokens") + assert hasattr(response.usage, "total_tokens") def validate_stream_chunk(chunk): @@ -38,7 +51,23 @@ def validate_stream_chunk(chunk): Validate streaming chunk structure from OpenAI responses API """ assert chunk is not None - assert isinstance(chunk.choices[0].delta.content, str) + assert hasattr(chunk, "choices") + assert len(chunk.choices) > 0 + assert hasattr(chunk.choices[0], "delta") + + # Some chunks might not have content in the delta + if ( + hasattr(chunk.choices[0].delta, "content") + and chunk.choices[0].delta.content is not None + ): + assert isinstance(chunk.choices[0].delta.content, str) + + assert hasattr(chunk, "id") + assert isinstance(chunk.id, str) + assert hasattr(chunk, "model") + assert isinstance(chunk.model, str) + assert hasattr(chunk, "created") + assert isinstance(chunk.created, int) def test_basic_response(): From acdc2d82667e2a3c72aa4c91ed281c9507faed2a Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:33:17 -0700 Subject: [PATCH 06/11] fix exception_type --- litellm/litellm_core_utils/exception_mapping_utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 7a0cffab7b..1350df639c 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -121,12 +121,13 @@ def extract_and_raise_litellm_exception( def exception_type( # type: ignore # noqa: PLR0915 - model, + model: Optional[str], original_exception, - custom_llm_provider, - completion_kwargs={}, - extra_kwargs={}, + custom_llm_provider: Optional[str] = None, + completion_kwargs: Optional[dict] = {}, + extra_kwargs: Optional[dict] = {}, ): + """Maps an LLM Provider Exception to OpenAI Exception Format""" if any( isinstance(original_exception, exc_type) From f632e93dd1f1e67dd69bf3452b0506f2b79e82eb Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:57:19 -0700 Subject: [PATCH 07/11] test_async_bad_request_bad_param_error --- .../test_openai_responses_api.py | 31 +++++++++++++++++++ .../test_openai_responses_api.py | 9 ++++++ 2 files changed, 40 insertions(+) diff --git a/tests/llm_responses_api_testing/test_openai_responses_api.py b/tests/llm_responses_api_testing/test_openai_responses_api.py index b711c93f80..37674551fe 100644 --- a/tests/llm_responses_api_testing/test_openai_responses_api.py +++ b/tests/llm_responses_api_testing/test_openai_responses_api.py @@ -795,3 +795,34 @@ async def test_openai_responses_litellm_router_with_metadata(): loaded_request_body["metadata"] == test_metadata ), "metadata in request body should match what was passed" mock_post.assert_called_once() + + +def test_bad_request_bad_param_error(): + """Raise a BadRequestError when an invalid parameter value is provided""" + try: + litellm.responses(model="gpt-4o", input="This should fail", temperature=2000) + pytest.fail("Expected BadRequestError but no exception was raised") + except litellm.BadRequestError as e: + print(f"Exception raised: {e}") + print(f"Exception type: {type(e)}") + print(f"Exception args: {e.args}") + print(f"Exception details: {e.__dict__}") + except Exception as e: + pytest.fail(f"Unexpected exception raised: {e}") + + +@pytest.mark.asyncio() +async def test_async_bad_request_bad_param_error(): + """Raise a BadRequestError when an invalid parameter value is provided""" + try: + await litellm.aresponses( + model="gpt-4o", input="This should fail", temperature=2000 + ) + pytest.fail("Expected BadRequestError but no exception was raised") + except litellm.BadRequestError as e: + print(f"Exception raised: {e}") + print(f"Exception type: {type(e)}") + print(f"Exception args: {e.args}") + print(f"Exception details: {e.__dict__}") + except Exception as e: + pytest.fail(f"Unexpected exception raised: {e}") diff --git a/tests/openai_endpoints_tests/test_openai_responses_api.py b/tests/openai_endpoints_tests/test_openai_responses_api.py index f853d53532..1dde8ebae6 100644 --- a/tests/openai_endpoints_tests/test_openai_responses_api.py +++ b/tests/openai_endpoints_tests/test_openai_responses_api.py @@ -97,3 +97,12 @@ def test_bad_request_error(): with pytest.raises(BadRequestError): # Trigger error with invalid model name client.responses.create(model="non-existent-model", input="This should fail") + + +def test_bad_request_bad_param_error(): + client = get_test_client() + with pytest.raises(BadRequestError): + # Trigger error with invalid model name + client.responses.create( + model="gpt-4o", input="This should fail", temperature=2000 + ) From c2ed7add37e9f1bae050dfbde4b2921d22b5b255 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 15:57:58 -0700 Subject: [PATCH 08/11] Add exception mapping for responses API --- .../exception_mapping_utils.py | 1 - litellm/responses/main.py | 170 ++++++++++-------- 2 files changed, 99 insertions(+), 72 deletions(-) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 1350df639c..82ba8d2e68 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -128,7 +128,6 @@ def exception_type( # type: ignore # noqa: PLR0915 extra_kwargs: Optional[dict] = {}, ): """Maps an LLM Provider Exception to OpenAI Exception Format""" - if any( isinstance(original_exception, exc_type) for exc_type in litellm.LITELLM_EXCEPTION_TYPES diff --git a/litellm/responses/main.py b/litellm/responses/main.py index ce70292e96..43f37bdbc6 100644 --- a/litellm/responses/main.py +++ b/litellm/responses/main.py @@ -58,15 +58,24 @@ async def aresponses( extra_query: Optional[Dict[str, Any]] = None, extra_body: Optional[Dict[str, Any]] = None, timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, **kwargs, ) -> Union[ResponsesAPIResponse, BaseResponsesAPIStreamingIterator]: """ Async: Handles responses API requests by reusing the synchronous function """ + local_vars = locals() try: loop = asyncio.get_event_loop() kwargs["aresponses"] = True + # get custom llm provider so we can use this for mapping exceptions + if custom_llm_provider is None: + _, custom_llm_provider, _, _ = litellm.get_llm_provider( + model=model, api_base=local_vars.get("base_url", None) + ) + func = partial( responses, input=input, @@ -91,6 +100,7 @@ async def aresponses( extra_query=extra_query, extra_body=extra_body, timeout=timeout, + custom_llm_provider=custom_llm_provider, **kwargs, ) @@ -104,7 +114,13 @@ async def aresponses( response = init_response return response except Exception as e: - raise e + raise litellm.exception_type( + model=model, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) @client @@ -133,85 +149,97 @@ def responses( extra_query: Optional[Dict[str, Any]] = None, extra_body: Optional[Dict[str, Any]] = None, timeout: Optional[Union[float, httpx.Timeout]] = None, + # LiteLLM specific params, + custom_llm_provider: Optional[str] = None, **kwargs, ): """ Synchronous version of the Responses API. Uses the synchronous HTTP handler to make requests. """ - litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore - litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) - _is_async = kwargs.pop("aresponses", False) is True - - # get llm provider logic - litellm_params = GenericLiteLLMParams(**kwargs) - model, custom_llm_provider, dynamic_api_key, dynamic_api_base = ( - litellm.get_llm_provider( - model=model, - custom_llm_provider=kwargs.get("custom_llm_provider", None), - api_base=litellm_params.api_base, - api_key=litellm_params.api_key, - ) - ) - - # get provider config - responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( - ProviderConfigManager.get_provider_responses_api_config( - model=model, - provider=litellm.LlmProviders(custom_llm_provider), - ) - ) - - if responses_api_provider_config is None: - raise litellm.BadRequestError( - model=model, - llm_provider=custom_llm_provider, - message=f"Responses API not available for custom_llm_provider={custom_llm_provider}, model: {model}", - ) - - # Get all parameters using locals() and combine with kwargs local_vars = locals() - local_vars.update(kwargs) - # Get ResponsesAPIOptionalRequestParams with only valid parameters - response_api_optional_params: ResponsesAPIOptionalRequestParams = ( - ResponsesAPIRequestUtils.get_requested_response_api_optional_param(local_vars) - ) + try: + litellm_logging_obj: LiteLLMLoggingObj = kwargs.get("litellm_logging_obj") # type: ignore + litellm_call_id: Optional[str] = kwargs.get("litellm_call_id", None) + _is_async = kwargs.pop("aresponses", False) is True - # Get optional parameters for the responses API - responses_api_request_params: Dict = ( - ResponsesAPIRequestUtils.get_optional_params_responses_api( - model=model, - responses_api_provider_config=responses_api_provider_config, - response_api_optional_params=response_api_optional_params, + # get llm provider logic + litellm_params = GenericLiteLLMParams(**kwargs) + model, custom_llm_provider, dynamic_api_key, dynamic_api_base = ( + litellm.get_llm_provider( + model=model, + custom_llm_provider=custom_llm_provider, + api_base=litellm_params.api_base, + api_key=litellm_params.api_key, + ) ) - ) - # Pre Call logging - litellm_logging_obj.update_environment_variables( - model=model, - user=user, - optional_params=dict(responses_api_request_params), - litellm_params={ - "litellm_call_id": litellm_call_id, - **responses_api_request_params, - }, - custom_llm_provider=custom_llm_provider, - ) + # get provider config + responses_api_provider_config: Optional[BaseResponsesAPIConfig] = ( + ProviderConfigManager.get_provider_responses_api_config( + model=model, + provider=litellm.LlmProviders(custom_llm_provider), + ) + ) - # Call the handler with _is_async flag instead of directly calling the async handler - response = base_llm_http_handler.response_api_handler( - model=model, - input=input, - responses_api_provider_config=responses_api_provider_config, - response_api_optional_request_params=responses_api_request_params, - custom_llm_provider=custom_llm_provider, - litellm_params=litellm_params, - logging_obj=litellm_logging_obj, - extra_headers=extra_headers, - extra_body=extra_body, - timeout=timeout or request_timeout, - _is_async=_is_async, - client=kwargs.get("client"), - ) + if responses_api_provider_config is None: + raise litellm.BadRequestError( + model=model, + llm_provider=custom_llm_provider, + message=f"Responses API not available for custom_llm_provider={custom_llm_provider}, model: {model}", + ) - return response + local_vars.update(kwargs) + # Get ResponsesAPIOptionalRequestParams with only valid parameters + response_api_optional_params: ResponsesAPIOptionalRequestParams = ( + ResponsesAPIRequestUtils.get_requested_response_api_optional_param( + local_vars + ) + ) + + # Get optional parameters for the responses API + responses_api_request_params: Dict = ( + ResponsesAPIRequestUtils.get_optional_params_responses_api( + model=model, + responses_api_provider_config=responses_api_provider_config, + response_api_optional_params=response_api_optional_params, + ) + ) + + # Pre Call logging + litellm_logging_obj.update_environment_variables( + model=model, + user=user, + optional_params=dict(responses_api_request_params), + litellm_params={ + "litellm_call_id": litellm_call_id, + **responses_api_request_params, + }, + custom_llm_provider=custom_llm_provider, + ) + + # Call the handler with _is_async flag instead of directly calling the async handler + response = base_llm_http_handler.response_api_handler( + model=model, + input=input, + responses_api_provider_config=responses_api_provider_config, + response_api_optional_request_params=responses_api_request_params, + custom_llm_provider=custom_llm_provider, + litellm_params=litellm_params, + logging_obj=litellm_logging_obj, + extra_headers=extra_headers, + extra_body=extra_body, + timeout=timeout or request_timeout, + _is_async=_is_async, + client=kwargs.get("client"), + ) + + return response + except Exception as e: + raise litellm.exception_type( + model=model, + custom_llm_provider=custom_llm_provider, + original_exception=e, + completion_kwargs=local_vars, + extra_kwargs=kwargs, + ) From daf02b4e196b3a408ac0996e32653da9a5b1c2dc Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 16:02:21 -0700 Subject: [PATCH 09/11] test_bad_request_bad_param_error --- ...t_openai_responses_api.py => test_e2e_openai_responses_api.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/openai_endpoints_tests/{test_openai_responses_api.py => test_e2e_openai_responses_api.py} (100%) diff --git a/tests/openai_endpoints_tests/test_openai_responses_api.py b/tests/openai_endpoints_tests/test_e2e_openai_responses_api.py similarity index 100% rename from tests/openai_endpoints_tests/test_openai_responses_api.py rename to tests/openai_endpoints_tests/test_e2e_openai_responses_api.py From bc8b6c42cfefd911ebd5a0e775dfe1a51b0e3593 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 16:43:25 -0700 Subject: [PATCH 10/11] Add stubbed routes to pass initial auth tests --- .../proxy/response_api_endpoints/endpoints.py | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/litellm/proxy/response_api_endpoints/endpoints.py b/litellm/proxy/response_api_endpoints/endpoints.py index 8649276b0e..f9ddf306a7 100644 --- a/litellm/proxy/response_api_endpoints/endpoints.py +++ b/litellm/proxy/response_api_endpoints/endpoints.py @@ -78,3 +78,93 @@ async def responses_api( proxy_logging_obj=proxy_logging_obj, version=version, ) + + +@router.get( + "/v1/responses/{response_id}", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +@router.get( + "/responses/{response_id}", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +async def get_response( + response_id: str, + request: Request, + fastapi_response: Response, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Get a response by ID. + + Follows the OpenAI Responses API spec: https://platform.openai.com/docs/api-reference/responses/get + + ```bash + curl -X GET http://localhost:4000/v1/responses/resp_abc123 \ + -H "Authorization: Bearer sk-1234" + ``` + """ + # TODO: Implement response retrieval logic + pass + + +@router.delete( + "/v1/responses/{response_id}", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +@router.delete( + "/responses/{response_id}", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +async def delete_response( + response_id: str, + request: Request, + fastapi_response: Response, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Delete a response by ID. + + Follows the OpenAI Responses API spec: https://platform.openai.com/docs/api-reference/responses/delete + + ```bash + curl -X DELETE http://localhost:4000/v1/responses/resp_abc123 \ + -H "Authorization: Bearer sk-1234" + ``` + """ + # TODO: Implement response deletion logic + pass + + +@router.get( + "/v1/responses/{response_id}/input_items", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +@router.get( + "/responses/{response_id}/input_items", + dependencies=[Depends(user_api_key_auth)], + tags=["responses"], +) +async def get_response_input_items( + response_id: str, + request: Request, + fastapi_response: Response, + user_api_key_dict: UserAPIKeyAuth = Depends(user_api_key_auth), +): + """ + Get input items for a response. + + Follows the OpenAI Responses API spec: https://platform.openai.com/docs/api-reference/responses/input-items + + ```bash + curl -X GET http://localhost:4000/v1/responses/resp_abc123/input_items \ + -H "Authorization: Bearer sk-1234" + ``` + """ + # TODO: Implement input items retrieval logic + pass From a6e04aeffb88d259f79fe1801e10ac54517c8712 Mon Sep 17 00:00:00 2001 From: Ishaan Jaff Date: Thu, 13 Mar 2025 20:09:32 -0700 Subject: [PATCH 11/11] exception_type --- litellm/litellm_core_utils/exception_mapping_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/litellm/litellm_core_utils/exception_mapping_utils.py b/litellm/litellm_core_utils/exception_mapping_utils.py index 82ba8d2e68..54d87cc42e 100644 --- a/litellm/litellm_core_utils/exception_mapping_utils.py +++ b/litellm/litellm_core_utils/exception_mapping_utils.py @@ -121,11 +121,11 @@ def extract_and_raise_litellm_exception( def exception_type( # type: ignore # noqa: PLR0915 - model: Optional[str], + model, original_exception, - custom_llm_provider: Optional[str] = None, - completion_kwargs: Optional[dict] = {}, - extra_kwargs: Optional[dict] = {}, + custom_llm_provider, + completion_kwargs={}, + extra_kwargs={}, ): """Maps an LLM Provider Exception to OpenAI Exception Format""" if any(