litellm/tests/test_ratelimit.py

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

160 lines
4.9 KiB
Python
Raw Normal View History

2024-04-03 04:49:13 +08:00
# %%
import asyncio
import os
import pytest
import random
from typing import Any
import sys
from dotenv import load_dotenv
load_dotenv()
sys.path.insert(
0, os.path.abspath("../")
) # Adds the parent directory to the system path
2024-04-03 04:49:13 +08:00
from pydantic import BaseModel
from litellm import utils, Router
COMPLETION_TOKENS = 5
base_model_list = [
{
"model_name": "gpt-3.5-turbo",
"litellm_params": {
"model": "gpt-3.5-turbo",
"api_key": os.getenv("OPENAI_API_KEY"),
"max_tokens": COMPLETION_TOKENS,
},
}
]
class RouterConfig(BaseModel):
rpm: int
tpm: int
@pytest.fixture(scope="function")
def router_factory():
2024-04-03 08:26:57 +08:00
def create_router(rpm, tpm, routing_strategy):
2024-04-03 04:49:13 +08:00
model_list = base_model_list.copy()
model_list[0]["rpm"] = rpm
model_list[0]["tpm"] = tpm
return Router(
model_list=model_list,
2024-04-03 08:26:57 +08:00
routing_strategy=routing_strategy,
enable_pre_call_checks=True,
2024-04-03 04:49:13 +08:00
debug_level="DEBUG",
)
return create_router
def generate_list_of_messages(num_messages):
2024-04-03 08:18:03 +08:00
"""
create num_messages new chat conversations
"""
2024-04-03 04:49:13 +08:00
return [
[{"role": "user", "content": f"{i}. Hey, how's it going? {random.random()}"}]
for i in range(num_messages)
]
def calculate_limits(list_of_messages):
2024-04-03 08:18:03 +08:00
"""
Return the min rpm and tpm level that would let all messages in list_of_messages be sent this minute
"""
2024-04-03 04:49:13 +08:00
rpm = len(list_of_messages)
2024-04-03 10:56:07 +08:00
tpm = sum(
(utils.token_counter(messages=m) + COMPLETION_TOKENS for m in list_of_messages)
)
2024-04-03 04:49:13 +08:00
return rpm, tpm
async def async_call(router: Router, list_of_messages) -> Any:
2024-04-03 10:56:07 +08:00
tasks = [
router.acompletion(model="gpt-3.5-turbo", messages=m) for m in list_of_messages
]
2024-04-03 04:49:13 +08:00
return await asyncio.gather(*tasks)
def sync_call(router: Router, list_of_messages) -> Any:
2024-04-03 10:56:07 +08:00
return [
router.completion(model="gpt-3.5-turbo", messages=m) for m in list_of_messages
]
2024-04-03 04:49:13 +08:00
class ExpectNoException(Exception):
pass
@pytest.mark.parametrize(
2024-04-03 08:18:03 +08:00
"num_try_send, num_allowed_send",
2024-04-03 04:49:13 +08:00
[
(2, 3), # sending as many as allowed, ExpectNoException
2024-04-03 10:56:07 +08:00
# (10, 10), # sending as many as allowed, ExpectNoException
2024-04-03 08:18:56 +08:00
(3, 2), # Sending more than allowed, ValueError
2024-04-03 10:56:07 +08:00
# (10, 9), # Sending more than allowed, ValueError
2024-04-03 04:49:13 +08:00
],
)
2024-04-03 10:56:07 +08:00
@pytest.mark.parametrize(
"sync_mode", [True, False]
) # Use parametrization for sync/async
2024-04-03 08:26:57 +08:00
@pytest.mark.parametrize(
"routing_strategy",
[
"usage-based-routing",
# "simple-shuffle", # dont expect to rate limit
# "least-busy", # dont expect to rate limit
2024-04-03 10:56:07 +08:00
# "latency-based-routing",
2024-04-03 08:26:57 +08:00
],
)
2024-04-03 10:56:07 +08:00
def test_rate_limit(
router_factory, num_try_send, num_allowed_send, sync_mode, routing_strategy
):
2024-04-03 08:18:03 +08:00
"""
Check if router.completion and router.acompletion can send more messages than they've been limited to.
Args:
router_factory: makes new router object, without any shared Global state
num_try_send (int): number of messages to try to send
num_allowed_send (int): max number of messages allowed to be sent in 1 minute
sync_mode (bool): if making sync (router.completion) or async (router.acompletion)
Raises:
ValueError: Error router throws when it hits rate limits
ExpectNoException: Signfies that no other error has happened. A NOP
"""
# Can send more messages then we're going to; so don't expect a rate limit error
args = locals()
print(f"args: {args}")
2024-04-03 10:56:07 +08:00
expected_exception = (
ExpectNoException if num_try_send <= num_allowed_send else ValueError
)
2024-04-03 08:18:03 +08:00
# if (
# num_try_send > num_allowed_send and sync_mode == False
# ): # async calls are made simultaneously - the check for collision would need to happen before the router call
# return
2024-04-03 08:18:03 +08:00
list_of_messages = generate_list_of_messages(max(num_try_send, num_allowed_send))
rpm, tpm = calculate_limits(list_of_messages[:num_allowed_send])
list_of_messages = list_of_messages[:num_try_send]
router: Router = router_factory(rpm, tpm, routing_strategy)
2024-04-03 04:49:13 +08:00
print(f"router: {router.model_list}")
2024-04-03 08:18:03 +08:00
with pytest.raises(expected_exception) as excinfo: # asserts correct type raised
2024-04-03 04:49:13 +08:00
if sync_mode:
results = sync_call(router, list_of_messages)
else:
results = asyncio.run(async_call(router, list_of_messages))
2024-04-03 08:18:03 +08:00
print(results)
if len([i for i in results if i is not None]) != num_try_send:
# since not all results got returned, raise rate limit error
raise ValueError("No deployments available for selected model")
2024-04-03 04:49:13 +08:00
raise ExpectNoException
2024-04-03 08:18:03 +08:00
print(expected_exception, excinfo)
if expected_exception is ValueError:
2024-04-03 04:49:13 +08:00
assert "No deployments available for selected model" in str(excinfo.value)
else:
2024-04-03 08:18:03 +08:00
assert len([i for i in results if i is not None]) == num_try_send