mirror of
https://github.com/tiennm99/litellm.git
synced 2026-07-11 17:05:43 +00:00
199 lines
6.8 KiB
Python
199 lines
6.8 KiB
Python
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
sys.path.insert(
|
|
0, os.path.abspath("../../..")
|
|
) # Adds the parent directory to the system path
|
|
|
|
|
|
from unittest.mock import MagicMock
|
|
|
|
from litellm.proxy.route_llm_request import route_request
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"route_type",
|
|
[
|
|
"atext_completion",
|
|
"acompletion",
|
|
"aembedding",
|
|
"aimage_generation",
|
|
"aspeech",
|
|
"atranscription",
|
|
"amoderation",
|
|
"arerank",
|
|
],
|
|
)
|
|
@pytest.mark.asyncio
|
|
async def test_route_request_dynamic_credentials(route_type):
|
|
data = {
|
|
"model": "openai/gpt-4o-mini-2024-07-18",
|
|
"api_key": "my-bad-key",
|
|
"api_base": "https://api.openai.com/v1 ",
|
|
}
|
|
llm_router = MagicMock()
|
|
# Ensure that the dynamic method exists on the llm_router mock.
|
|
getattr(llm_router, route_type).return_value = "fake_response"
|
|
|
|
response = await route_request(data, llm_router, None, route_type)
|
|
# Optionally verify the response if needed:
|
|
assert response == "fake_response"
|
|
# Now assert that the dynamic method was called once with the expected kwargs.
|
|
getattr(llm_router, route_type).assert_called_once_with(**data)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_route_request_no_model_required():
|
|
"""Test route types that don't require model parameter"""
|
|
test_cases = [
|
|
"amoderation",
|
|
"aget_responses",
|
|
"adelete_responses",
|
|
"avector_store_create",
|
|
"avector_store_search",
|
|
]
|
|
|
|
for route_type in test_cases:
|
|
# Test data without model parameter
|
|
data = {"input": "test input", "api_key": "test-key"}
|
|
|
|
llm_router = MagicMock()
|
|
getattr(llm_router, route_type).return_value = "fake_response"
|
|
|
|
response = await route_request(data, llm_router, None, route_type)
|
|
|
|
# Verify response
|
|
assert response == "fake_response"
|
|
# Verify the method was called with correct parameters
|
|
getattr(llm_router, route_type).assert_called_once_with(**data)
|
|
|
|
# Reset mock for next iteration
|
|
llm_router.reset_mock()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_route_request_no_model_required_with_router_settings():
|
|
"""Test route types that don't require model parameter with router settings"""
|
|
test_cases = [
|
|
"amoderation",
|
|
"aget_responses",
|
|
"adelete_responses",
|
|
"avector_store_create",
|
|
"avector_store_search",
|
|
]
|
|
|
|
for route_type in test_cases:
|
|
# Test data with model parameter (it will be ignored for these route types)
|
|
data = {
|
|
"input": "test input",
|
|
"model": "test-model", # Include dummy model to avoid KeyError
|
|
}
|
|
|
|
llm_router = MagicMock()
|
|
# Set up router settings
|
|
llm_router.router_general_settings.pass_through_all_models = False
|
|
llm_router.default_deployment = None
|
|
llm_router.pattern_router.patterns = []
|
|
llm_router.model_names = [] # Empty model names list
|
|
llm_router.get_model_ids.return_value = [] # Empty model IDs
|
|
llm_router.model_group_alias = None # No model group alias
|
|
|
|
# Mock the async route call
|
|
getattr(llm_router, route_type).return_value = "fake_response"
|
|
|
|
# Run the request
|
|
response = await route_request(data, llm_router, None, route_type)
|
|
|
|
# Assert the mocked method was called with expected input
|
|
assert response == "fake_response"
|
|
getattr(llm_router, route_type).assert_called_once_with(**data)
|
|
|
|
# Reset the mock for the next route
|
|
llm_router.reset_mock()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_route_request_no_model_required_with_router_settings_and_no_router():
|
|
"""Test route types that don't require model parameter with router settings and no router"""
|
|
from unittest.mock import patch
|
|
|
|
import litellm
|
|
from litellm.proxy.route_llm_request import route_request
|
|
|
|
data = {
|
|
"model": "my-model-id",
|
|
"api_key": "my-api-key",
|
|
"messages": [{"role": "user", "content": "what llm are you"}],
|
|
}
|
|
|
|
with patch.object(
|
|
litellm, "acompletion", return_value="fake_response"
|
|
) as mock_completion:
|
|
await route_request(data, None, "gpt-3.5-turbo", "acompletion")
|
|
|
|
mock_completion.assert_called_once_with(**data)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_route_request_with_invalid_router_params():
|
|
"""
|
|
Test that route_request filters out invalid Router init params from 'user_config'.
|
|
This covers the fix for https://github.com/BerriAI/litellm/issues/19693
|
|
"""
|
|
import litellm
|
|
from litellm.router import Router
|
|
from unittest.mock import AsyncMock
|
|
|
|
# Mock data with user_config containing invalid keys (simulating DB entry)
|
|
data = {
|
|
"model": "gpt-3.5-turbo",
|
|
"user_config": {
|
|
"model_list": [
|
|
{
|
|
"model_name": "gpt-3.5-turbo",
|
|
"litellm_params": {"model": "gpt-3.5-turbo", "api_key": "test"},
|
|
}
|
|
],
|
|
"model_alias_map": {"alias": "real_model"}, # INVALID PARAM
|
|
"invalid_garbage_key": "crash_me", # INVALID PARAM
|
|
},
|
|
}
|
|
|
|
# We expect Router(**config) to succeed because of the filtering.
|
|
# If filtering fails, this will raise TypeError and fail the test.
|
|
try:
|
|
# route_request calls getattr(user_router, route_type)(**data)
|
|
# We'll mock the internal call to avoid making real network requests
|
|
with pytest.MonkeyPatch.context() as m:
|
|
# Mock the method that gets called on the router instance
|
|
# We don't easily have access to the instance created INSIDE existing route_request
|
|
# So we will wrap litellm.Router to spy on it or verify it doesn't crash
|
|
|
|
original_router_init = litellm.Router.__init__
|
|
|
|
def safe_router_init(self, **kwargs):
|
|
# Verify that invalid keys are NOT present in kwargs
|
|
assert "model_alias_map" not in kwargs
|
|
assert "invalid_garbage_key" not in kwargs
|
|
# Call original init (which would raise TypeError if invalid keys were present)
|
|
original_router_init(self, **kwargs)
|
|
|
|
m.setattr(litellm.Router, "__init__", safe_router_init)
|
|
|
|
# Use 'acompletion' as the route_type
|
|
# We also need to mock the completion method to avoid real calls
|
|
m.setattr(Router, "acompletion", AsyncMock(return_value="success"))
|
|
|
|
response = await route_request(data, None, None, "acompletion")
|
|
assert response == "success"
|
|
|
|
except TypeError as e:
|
|
pytest.fail(
|
|
f"route_request raised TypeError, implying invalid params were passed to Router: {e}"
|
|
)
|
|
except Exception:
|
|
# Other exceptions might happen (e.g. valid config issues) but we care about TypeError here
|
|
pass
|