litellm/tests/proxy_unit_tests/test_proxy_exception_mapping.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

325 lines
11 KiB
Python
Raw Permalink Normal View History

2023-12-15 10:58:01 +08:00
# test that the proxy actually does exception mapping to the OpenAI format
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
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
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"
asyncio.run(initialize(config=config_fp))
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
# raise openai.AuthenticationError
2023-12-15 10:58:01 +08:00
def test_chat_completion_exception(client):
try:
# Your test data
test_data = {
"model": "gpt-3.5-turbo",
"messages": [
2023-12-25 16:40:38 +08:00
{"role": "user", "content": "hi"},
],
"max_tokens": 10,
}
response = client.post("/chat/completions", json=test_data)
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 (
"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-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
# 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
# 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
)
assert isinstance(openai_exception, openai.AuthenticationError)
except Exception as e:
pytest.fail(f"LiteLLM Proxy test failed. Exception {str(e)}")
2023-12-25 16:40:38 +08:00
# raise openai.AuthenticationError
@mock.patch(
"litellm.proxy.proxy_server.llm_router.acompletion",
return_value=invalid_authentication_error_response,
)
def test_chat_completion_exception_azure(mock_acompletion, client):
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"},
],
"max_tokens": 10,
}
response = client.post("/chat/completions", json=test_data)
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,
secret_fields=mock.ANY,
)
json_response = response.json()
print("keys in json response", json_response.keys())
assert json_response.keys() == {"error"}
# 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
)
print(openai_exception)
assert isinstance(openai_exception, openai.AuthenticationError)
2023-12-15 10:58:01 +08:00
except Exception as e:
pytest.fail(f"LiteLLM Proxy test failed. Exception {str(e)}")
# raise openai.AuthenticationError
@mock.patch(
"litellm.proxy.proxy_server.llm_router.aembedding",
return_value=invalid_authentication_error_response,
)
def test_embedding_auth_exception_azure(mock_aembedding, client):
try:
# Your test data
2023-12-25 16:40:38 +08:00
test_data = {"model": "azure-embedding", "input": ["hi"]}
response = client.post("/embeddings", json=test_data)
mock_aembedding.assert_called_once_with(
**test_data,
metadata=mock.ANY,
proxy_server_request=mock.ANY,
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,
)
print("Response from proxy=", response)
json_response = response.json()
print("keys in json response", json_response.keys())
assert json_response.keys() == {"error"}
# 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
)
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)}")
# raise openai.BadRequestError
# chat/completions openai
def test_exception_openai_bad_model(client):
try:
# Your test data
test_data = {
"model": "azure/GPT-12",
"messages": [
2023-12-25 16:40:38 +08:00
{"role": "user", "content": "hi"},
],
"max_tokens": 10,
}
response = client.post("/chat/completions", json=test_data)
json_response = response.json()
print("keys in json response", json_response.keys())
assert json_response.keys() == {"error"}
# 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
)
print("Type of exception=", type(openai_exception))
assert isinstance(openai_exception, openai.BadRequestError)
except Exception as e:
pytest.fail(f"LiteLLM Proxy test failed. Exception {str(e)}")
2023-12-25 16:40:38 +08:00
# chat/completions any model
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"},
],
"max_tokens": 10,
}
response = client.post("/chat/completions", json=test_data)
json_response = response.json()
assert json_response.keys() == {"error"}
# 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
)
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
)
except Exception as e:
pytest.fail(f"LiteLLM Proxy test failed. Exception {str(e)}")
# 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"]}
response = client.post("/embeddings", json=test_data)
print("Response from proxy=", response)
print(response.json())
json_response = response.json()
print("keys in json response", json_response.keys())
assert json_response.keys() == {"error"}
# 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
)
print("Exception raised=", openai_exception)
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
)
except Exception as e:
pytest.fail(f"LiteLLM Proxy test failed. Exception {str(e)}")
# raise openai.BadRequestError
@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):
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)
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,
secret_fields=mock.ANY,
)
json_response = response.json()
print("keys in json response", json_response.keys())
assert json_response.keys() == {"error"}
assert json_response == context_length_exceeded_error_response_dict
# 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)}")