2023-12-15 10:58:01 +08:00
# test that the proxy actually does exception mapping to the OpenAI format
2024-05-03 05:42:20 +08:00
import json
2024-06-30 04:29:28 +08:00
import os
import sys
from unittest import mock
2023-12-15 10:58:01 +08:00
from dotenv import load_dotenv
load_dotenv ( )
2024-06-30 04:29:28 +08:00
import asyncio
import io
import os
2023-12-25 16:40:38 +08:00
2023-12-15 10:58:01 +08:00
sys . path . insert (
0 , os . path . abspath ( " ../.. " )
2023-12-25 16:40:38 +08:00
) # Adds the parent directory to the system path
2024-06-30 04:29:28 +08:00
import openai
2023-12-15 10:58:01 +08:00
import pytest
2024-05-03 05:42:20 +08:00
from fastapi import Response
2024-06-30 04:29:28 +08:00
from fastapi . testclient import TestClient
import litellm
from litellm . proxy . proxy_server import ( # Replace with the actual module where your FastAPI router is defined
initialize ,
2023-12-25 16:40:38 +08:00
router ,
save_worker_config ,
2024-06-30 04:29:28 +08:00
)
2023-12-25 16:40:38 +08:00
2024-05-03 05:42:20 +08:00
invalid_authentication_error_response = Response (
status_code = 401 ,
content = json . dumps ( { " error " : " Invalid Authentication " } ) ,
)
context_length_exceeded_error_response_dict = {
" error " : {
" message " : " AzureException - Error code: 400 - { ' error ' : { ' message ' : \" This model ' s maximum context length is 4096 tokens. However, your messages resulted in 10007 tokens. Please reduce the length of the messages. \" , ' type ' : ' invalid_request_error ' , ' param ' : ' messages ' , ' code ' : ' context_length_exceeded ' }} " ,
" type " : None ,
" param " : None ,
" code " : 400 ,
} ,
}
context_length_exceeded_error_response = Response (
status_code = 400 ,
content = json . dumps ( context_length_exceeded_error_response_dict ) ,
)
2023-12-15 10:58:01 +08:00
@pytest.fixture
def client ( ) :
filepath = os . path . dirname ( os . path . abspath ( __file__ ) )
config_fp = f " { filepath } /test_configs/test_bad_config.yaml "
2024-01-04 20:58:18 +08:00
asyncio . run ( initialize ( config = config_fp ) )
2024-01-16 02:43:26 +08:00
from litellm . proxy . proxy_server import app
2023-12-15 10:58:01 +08:00
return TestClient ( app )
2023-12-25 16:40:38 +08:00
2023-12-15 12:06:07 +08:00
# raise openai.AuthenticationError
2023-12-15 10:58:01 +08:00
def test_chat_completion_exception ( client ) :
try :
2023-12-15 12:06:07 +08:00
# Your test data
test_data = {
" model " : " gpt-3.5-turbo " ,
" messages " : [
2023-12-25 16:40:38 +08:00
{ " role " : " user " , " content " : " hi " } ,
2023-12-15 12:06:07 +08:00
] ,
" max_tokens " : 10 ,
}
response = client . post ( " /chat/completions " , json = test_data )
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2024-06-30 04:29:28 +08:00
print ( " ERROR= " , json_response [ " error " ] )
assert isinstance ( json_response [ " error " ] [ " message " ] , str )
assert (
2024-08-28 07:14:30 +08:00
" litellm.AuthenticationError: AuthenticationError "
2024-07-19 13:05:10 +08:00
in json_response [ " error " ] [ " message " ]
2024-06-30 04:29:28 +08:00
)
2024-01-16 02:43:26 +08:00
2024-07-31 03:35:46 +08:00
code_in_error = json_response [ " error " ] [ " code " ]
# OpenAI SDK required code to be STR, https://github.com/BerriAI/litellm/issues/4970
2024-07-31 03:38:33 +08:00
# If we look on official python OpenAI lib, the code should be a string:
# https://github.com/openai/openai-python/blob/195c05a64d39c87b2dfdf1eca2d339597f1fce03/src/openai/types/shared/error_object.py#L11
# Related LiteLLM issue: https://github.com/BerriAI/litellm/discussions/4834
2024-07-31 03:35:46 +08:00
assert type ( code_in_error ) == str
2023-12-15 12:06:07 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2023-12-15 12:06:07 +08:00
assert isinstance ( openai_exception , openai . AuthenticationError )
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2023-12-15 16:40:42 +08:00
2023-12-25 16:40:38 +08:00
2023-12-15 16:40:42 +08:00
# raise openai.AuthenticationError
2024-05-03 05:42:20 +08:00
@mock.patch (
" litellm.proxy.proxy_server.llm_router.acompletion " ,
return_value = invalid_authentication_error_response ,
)
def test_chat_completion_exception_azure ( mock_acompletion , client ) :
2023-12-15 12:06:07 +08:00
try :
# Your test data
test_data = {
" model " : " azure-gpt-3.5-turbo " ,
" messages " : [
2023-12-25 16:40:38 +08:00
{ " role " : " user " , " content " : " hi " } ,
2023-12-15 12:06:07 +08:00
] ,
" max_tokens " : 10 ,
}
response = client . post ( " /chat/completions " , json = test_data )
2024-05-03 05:42:20 +08:00
mock_acompletion . assert_called_once_with (
* * test_data ,
litellm_call_id = mock . ANY ,
litellm_logging_obj = mock . ANY ,
request_timeout = mock . ANY ,
metadata = mock . ANY ,
proxy_server_request = mock . ANY ,
2025-07-20 07:35:05 +08:00
secret_fields = mock . ANY ,
2024-05-03 05:42:20 +08:00
)
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2023-12-15 12:06:07 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2023-12-21 14:53:07 +08:00
print ( openai_exception )
2023-12-15 12:06:07 +08:00
assert isinstance ( openai_exception , openai . AuthenticationError )
2023-12-15 10:58:01 +08:00
except Exception as e :
2023-12-15 16:04:51 +08:00
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2023-12-15 16:40:42 +08:00
# raise openai.AuthenticationError
2024-05-03 05:42:20 +08:00
@mock.patch (
" litellm.proxy.proxy_server.llm_router.aembedding " ,
return_value = invalid_authentication_error_response ,
)
def test_embedding_auth_exception_azure ( mock_aembedding , client ) :
2023-12-15 16:40:42 +08:00
try :
# Your test data
2023-12-25 16:40:38 +08:00
test_data = { " model " : " azure-embedding " , " input " : [ " hi " ] }
2023-12-15 16:40:42 +08:00
response = client . post ( " /embeddings " , json = test_data )
2024-05-03 05:42:20 +08:00
mock_aembedding . assert_called_once_with (
* * test_data ,
metadata = mock . ANY ,
proxy_server_request = mock . ANY ,
2025-07-20 07:35:05 +08:00
secret_fields = mock . ANY ,
[Perf] Embeddings: Use router's O(1) lookup and shared sessions (#16344)
* Refactor proxy embeddings to use shared processor
- allow ProxyBaseLLMRequestProcessing to accept the aembedding route so embeddings requests reuse the base pipeline hooks
- route embeddings requests through base_process_llm_request, sharing logging, hook execution, retries, and header handling with chat/responses
- tighten token array decoding logic by using router deployment lookups and the unified error handler
* Fix: Correctly process embedding requests with token arrays
The `test_embedding_input_array_of_tokens` test was failing due to a regression that caused embedding requests with token arrays to be processed incorrectly. This prevented the `aembedding` function from being called as expected.
This was caused by a combination of three distinct issues:
1. In `litellm/proxy/common_request_processing.py`, the `function_setup` utility was called with `aembedding` as the `original_function` for embedding routes. This has been corrected to `embedding` to ensure proper request setup.
2. In `litellm/proxy/proxy_server.py`, a `TypeError` occurred because the `get_deployment` method was called with the `model_name` keyword argument instead of the expected `model_id`. This has been corrected. Additionally, the check for token arrays was improved to validate that all elements in the input subarray are integers.
3. In `litellm/proxy/litellm_pre_call_utils.py`, the check for the `enforced_params` enterprise feature was too strict. It blocked valid requests even when the `enforced_params` list was empty. The condition has been adjusted to trigger the check only for non-empty lists.
Finally, the `test_embedding_input_array_of_tokens` assertion was updated to be more robust. The previous `assert_called_once_with` was overly strict, causing failures when unrelated internal parameters were added to the function call. The test now first asserts that `aembedding` is called and then separately verifies the `model` and `input` arguments. This makes the test more resilient to future changes without sacrificing its ability to catch regressions.
* test: align proxy embedding assertions
Update the embedding proxy test to match the new request pipeline: keep the data the proxy builds, expect the extra control kwargs, let the post-call hook return the actual response, and assert the normalized 'embeddings' hook type. This proves the refactor still forwards metadata and returns the mocked payload.
* Update proxy exception test
The proxy now forwards additional kwargs (request_timeout, litellm_call_id, litellm_logging_obj) to llm_router.aembedding. The test needs to accept these to match the real call signature and keep validating the error path instead of the kwargs list.
* testing: unsure of this change
I don't remember why I changed this, will revert and see if any tests fail since the manual test isn't failing without it.
* fix: remove unrelated change
This change was not related to the embeddings refactor and actually belonged to a different branch.
2025-11-15 01:21:45 +08:00
request_timeout = mock . ANY ,
litellm_call_id = mock . ANY ,
litellm_logging_obj = mock . ANY ,
2024-05-03 05:42:20 +08:00
)
2023-12-15 16:40:42 +08:00
print ( " Response from proxy= " , response )
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2023-12-15 16:40:42 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2023-12-15 16:40:42 +08:00
print ( " Exception raised= " , openai_exception )
assert isinstance ( openai_exception , openai . AuthenticationError )
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2023-12-15 16:04:51 +08:00
# raise openai.BadRequestError
2023-12-15 16:32:24 +08:00
# chat/completions openai
2023-12-15 16:04:51 +08:00
def test_exception_openai_bad_model ( client ) :
try :
# Your test data
test_data = {
2023-12-15 16:32:24 +08:00
" model " : " azure/GPT-12 " ,
2023-12-15 16:04:51 +08:00
" messages " : [
2023-12-25 16:40:38 +08:00
{ " role " : " user " , " content " : " hi " } ,
2023-12-15 16:04:51 +08:00
] ,
" max_tokens " : 10 ,
}
response = client . post ( " /chat/completions " , json = test_data )
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2023-12-15 16:04:51 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2023-12-15 16:04:51 +08:00
print ( " Type of exception= " , type ( openai_exception ) )
2024-02-13 08:25:35 +08:00
assert isinstance ( openai_exception , openai . BadRequestError )
2023-12-15 16:04:51 +08:00
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2023-12-25 16:40:38 +08:00
2023-12-15 16:32:24 +08:00
# chat/completions any model
2023-12-15 16:04:51 +08:00
def test_chat_completion_exception_any_model ( client ) :
try :
# Your test data
test_data = {
" model " : " Lite-GPT-12 " ,
" messages " : [
2023-12-25 16:40:38 +08:00
{ " role " : " user " , " content " : " hi " } ,
2023-12-15 16:04:51 +08:00
] ,
" max_tokens " : 10 ,
}
response = client . post ( " /chat/completions " , json = test_data )
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
assert json_response . keys ( ) == { " error " }
2023-12-15 16:04:51 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2024-01-05 01:15:16 +08:00
assert isinstance ( openai_exception , openai . BadRequestError )
2024-04-19 06:46:49 +08:00
_error_message = openai_exception . message
2024-08-15 23:52:28 +08:00
assert (
" /chat/completions: Invalid model name passed in model=Lite-GPT-12 "
in str ( _error_message )
2024-06-08 05:07:58 +08:00
)
2023-12-15 16:04:51 +08:00
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2023-12-15 16:32:24 +08:00
# embeddings any model
def test_embedding_exception_any_model ( client ) :
try :
# Your test data
2023-12-25 16:40:38 +08:00
test_data = { " model " : " Lite-GPT-12 " , " input " : [ " hi " ] }
2023-12-15 16:32:24 +08:00
response = client . post ( " /embeddings " , json = test_data )
print ( " Response from proxy= " , response )
2024-01-16 02:43:26 +08:00
print ( response . json ( ) )
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2023-12-15 16:32:24 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
2023-12-25 16:40:38 +08:00
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
2023-12-15 16:32:24 +08:00
print ( " Exception raised= " , openai_exception )
2024-01-05 01:15:16 +08:00
assert isinstance ( openai_exception , openai . BadRequestError )
2024-04-19 06:46:49 +08:00
_error_message = openai_exception . message
2024-08-15 23:52:28 +08:00
assert " /embeddings: Invalid model name passed in model=Lite-GPT-12 " in str (
2024-06-08 05:07:58 +08:00
_error_message
)
2023-12-15 16:32:24 +08:00
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )
2024-01-16 02:43:26 +08:00
# raise openai.BadRequestError
2024-05-03 05:42:20 +08:00
@mock.patch (
" litellm.proxy.proxy_server.llm_router.acompletion " ,
return_value = context_length_exceeded_error_response ,
)
def test_chat_completion_exception_azure_context_window ( mock_acompletion , client ) :
2024-01-16 02:43:26 +08:00
try :
# Your test data
test_data = {
" model " : " working-azure-gpt-3.5-turbo " ,
" messages " : [
{ " role " : " user " , " content " : " hi " * 10000 } ,
] ,
" max_tokens " : 10 ,
}
response = None
response = client . post ( " /chat/completions " , json = test_data )
print ( " got response from server " , response )
2024-05-03 05:42:20 +08:00
mock_acompletion . assert_called_once_with (
* * test_data ,
litellm_call_id = mock . ANY ,
litellm_logging_obj = mock . ANY ,
request_timeout = mock . ANY ,
metadata = mock . ANY ,
proxy_server_request = mock . ANY ,
2025-07-20 07:35:05 +08:00
secret_fields = mock . ANY ,
2024-05-03 05:42:20 +08:00
)
2024-01-16 02:43:26 +08:00
json_response = response . json ( )
print ( " keys in json response " , json_response . keys ( ) )
assert json_response . keys ( ) == { " error " }
2024-05-03 05:42:20 +08:00
assert json_response == context_length_exceeded_error_response_dict
2024-01-16 02:43:26 +08:00
# make an openai client to call _make_status_error_from_response
openai_client = openai . OpenAI ( api_key = " anything " )
openai_exception = openai_client . _make_status_error_from_response (
response = response
)
print ( " exception from proxy " , openai_exception )
assert isinstance ( openai_exception , openai . BadRequestError )
print ( " passed exception is of type BadRequestError " )
except Exception as e :
pytest . fail ( f " LiteLLM Proxy test failed. Exception { str ( e ) } " )