Files
litellm/tests/test_litellm/models/test_models.py
T
2026-06-06 20:59:33 -07:00

543 lines
18 KiB
Python

"""
Tests for backend domain models.
"""
from datetime import datetime
import pytest
from litellm.models.access_group import LiteLLM_AccessGroupTable
from litellm.models.budget import (
LiteLLM_BudgetTable,
LiteLLM_BudgetTableFull,
LiteLLM_TeamMemberTable,
)
from litellm.models.config import LiteLLM_Config
from litellm.models.credentials import CreateCredentialItem, CredentialItem
from litellm.models.end_user import LiteLLM_EndUserTable
from litellm.models.managed_files import (
LiteLLM_ManagedFileTable,
LiteLLM_ManagedObjectTable,
LiteLLM_ManagedVectorStoresTable,
)
from litellm.models.mcp_server import LiteLLM_MCPServerTable
from litellm.models.model import LiteLLM_ProxyModelTable
from litellm.models.object_permission import LiteLLM_ObjectPermissionTable
from litellm.models.organization import LiteLLM_OrganizationTable
from litellm.models.project import LiteLLM_ProjectTable
from litellm.models.skills import LiteLLM_SkillsTable
from litellm.models.spend_logs import LiteLLM_ErrorLogs, LiteLLM_SpendLogs
from litellm.models.tag import LiteLLM_TagTable
from litellm.models.team import (
LiteLLM_DeletedTeamTable,
LiteLLM_TeamTable,
LiteLLM_TeamTableCachedObj,
)
from litellm.models.team_membership import LiteLLM_TeamMembership
from litellm.models.user import LiteLLM_UserTable
from litellm.models.verification_token import (
LiteLLM_DeletedVerificationToken,
LiteLLM_VerificationToken,
)
class TestBudget:
def test_budget_creation(self):
budget = LiteLLM_BudgetTable(
budget_id="test-budget-id",
max_budget=100.0,
soft_budget=80.0,
tpm_limit=1000,
rpm_limit=100,
model_max_budget={"gpt-4": 50.0},
budget_duration="monthly",
allowed_models=["gpt-4"],
)
assert budget.budget_id == "test-budget-id"
assert budget.max_budget == 100.0
assert budget.soft_budget == 80.0
assert budget.tpm_limit == 1000
assert budget.rpm_limit == 100
assert budget.model_max_budget == {"gpt-4": 50.0}
assert budget.budget_duration == "monthly"
assert budget.allowed_models == ["gpt-4"]
def test_budget_defaults(self):
budget = LiteLLM_BudgetTable()
assert budget.budget_id is None
assert budget.max_budget is None
assert budget.allowed_models is None
class TestCredentials:
def test_credentials_creation(self):
creds = CredentialItem(
credential_name="test-cred",
credential_values={"api_key": "secret123"},
credential_info={"provider": "openai"},
)
assert creds.credential_name == "test-cred"
assert creds.credential_values["api_key"] == "secret123"
assert creds.credential_info["provider"] == "openai"
def test_create_credential_item_accepts_model_id(self):
item = CreateCredentialItem(
credential_name="from-model",
credential_info={},
model_id="model-123",
)
assert item.model_id == "model-123"
assert item.credential_values is None
def test_create_credential_item_requires_values_or_model_id(self):
with pytest.raises(
ValueError, match="Either credential_values or model_id must be set"
):
CreateCredentialItem(credential_name="bad", credential_info={})
class TestModel:
def test_model_creation(self):
model = LiteLLM_ProxyModelTable(
model_id="test-model-id",
model_name="gpt-4",
litellm_params={"model": "gpt-4", "api_key": "test"},
model_info={"team_id": "team-123", "team_public_model_name": "my-gpt4"},
)
assert model.model_id == "test-model-id"
assert model.model_name == "gpt-4"
assert model.team_id == "team-123"
assert model.team_public_model_name == "my-gpt4"
def test_is_blocked(self):
model_blocked = LiteLLM_ProxyModelTable(
model_id="m1", model_name="test", litellm_params={}, blocked=True
)
model_unblocked = LiteLLM_ProxyModelTable(
model_id="m2", model_name="test", litellm_params={}, blocked=False
)
assert model_blocked.is_blocked
assert not model_unblocked.is_blocked
def test_parses_json_string_fields(self):
model = LiteLLM_ProxyModelTable(
model_id="m1",
model_name="gpt-4",
litellm_params='{"model": "gpt-4"}',
model_info='{"team_id": "t1"}',
)
assert model.litellm_params == {"model": "gpt-4"}
assert model.model_info == {"team_id": "t1"}
def test_team_helpers_none_when_no_model_info(self):
model = LiteLLM_ProxyModelTable(
model_id="m1", model_name="gpt-4", litellm_params={}, model_info=None
)
assert model.team_id is None
assert model.team_public_model_name is None
class TestObjectPermission:
def test_object_permission_creation(self):
perm = LiteLLM_ObjectPermissionTable(
object_permission_id="test-perm-id",
mcp_servers=["server1", "server2"],
vector_stores=["vs1"],
agents=["agent1"],
models=["gpt-4"],
blocked_tools=["dangerous_tool"],
)
assert perm.object_permission_id == "test-perm-id"
assert len(perm.mcp_servers) == 2
assert perm.vector_stores == ["vs1"]
assert perm.agents == ["agent1"]
assert perm.models == ["gpt-4"]
assert perm.blocked_tools == ["dangerous_tool"]
def test_object_permission_tool_permissions(self):
perm = LiteLLM_ObjectPermissionTable(
object_permission_id="perm-tools",
mcp_tool_permissions={"server1": ["tool1", "tool2"]},
)
assert perm.mcp_tool_permissions == {"server1": ["tool1", "tool2"]}
class TestOrganization:
def test_organization_creation(self):
org = LiteLLM_OrganizationTable(
organization_id="org-123",
organization_alias="My Org",
budget_id="budget-123",
models=["gpt-4", "claude-3"],
spend=50.0,
created_by="admin",
updated_by="admin",
)
assert org.organization_id == "org-123"
assert org.organization_alias == "My Org"
assert len(org.models) == 2
class TestProject:
def test_project_creation(self):
project = LiteLLM_ProjectTable(
project_id="proj-123",
project_alias="My Project",
team_id="team-123",
blocked=False,
)
assert project.project_id == "proj-123"
assert not project.is_blocked
class TestTeam:
def test_team_creation(self):
team = LiteLLM_TeamTable(
team_id="team-123",
team_alias="Engineering",
admins=["user1"],
members=["user2", "user3"],
models=["gpt-4"],
max_budget=1000.0,
spend=100.0,
)
assert team.team_id == "team-123"
assert team.team_alias == "Engineering"
assert team.admins == ["user1"]
assert team.members == ["user2", "user3"]
assert team.models == ["gpt-4"]
assert team.max_budget == 1000.0
def test_members_with_roles_parsing(self):
team = LiteLLM_TeamTable(
team_id="t2",
members_with_roles=[
{"user_id": "user1", "role": "admin"},
{"user_id": "user2", "role": "user"},
],
)
assert len(team.members_with_roles) == 2
assert team.members_with_roles[0].user_id == "user1"
assert team.members_with_roles[0].role == "admin"
def test_members_with_roles_empty_dict_coerced(self):
team = LiteLLM_TeamTable(team_id="t3", members_with_roles={})
assert team.members_with_roles == []
def test_json_string_fields_parsed(self):
team = LiteLLM_TeamTable(
team_id="t4",
metadata='{"k": "v"}',
model_max_budget='{"gpt-4": 5.0}',
)
assert team.metadata == {"k": "v"}
assert team.model_max_budget == {"gpt-4": 5.0}
def test_cached_team(self):
cached = LiteLLM_TeamTableCachedObj(
team_id="t1", last_refreshed_at=1234567890.0
)
assert cached.last_refreshed_at == 1234567890.0
def test_deleted_team(self):
deleted = LiteLLM_DeletedTeamTable(
team_id="t1",
deleted_by="admin",
deleted_at=datetime.utcnow(),
)
assert deleted.deleted_by == "admin"
assert deleted.deleted_at is not None
class TestUser:
def test_user_creation(self):
user = LiteLLM_UserTable(
user_id="user-123",
user_email="test@example.com",
teams=["team1", "team2"],
max_budget=100.0,
spend=25.0,
)
assert user.user_id == "user-123"
assert user.user_email == "test@example.com"
assert len(user.teams) == 2
def test_is_over_budget(self):
user = LiteLLM_UserTable(user_id="u1", max_budget=100.0, spend=150.0)
user_no_budget = LiteLLM_UserTable(user_id="u2", spend=1000.0)
assert user.is_over_budget()
assert not user_no_budget.is_over_budget()
def test_has_model_access(self):
user_with_models = LiteLLM_UserTable(user_id="u1", models=["gpt-4"])
user_no_models = LiteLLM_UserTable(user_id="u2", models=[])
assert user_with_models.has_model_access("gpt-4")
assert not user_with_models.has_model_access("gpt-3")
assert user_no_models.has_model_access("any-model")
def test_password_hash_excluded_from_serialization(self):
from litellm.proxy._types import LiteLLM_UserTableWithKeyCount
secret = "$2b$12$abcdefghijklmnopqrstuv"
user = LiteLLM_UserTable(user_id="u1", user_email="a@b.c", password=secret)
assert user.password == secret
assert "password" not in user.model_dump()
assert "password" not in user.model_dump_json()
with_keys = LiteLLM_UserTableWithKeyCount(
user_id="u1", user_email="a@b.c", password=secret, key_count=2
)
assert with_keys.password == secret
assert "password" not in with_keys.model_dump()
assert "password" not in with_keys.model_dump_json()
class TestVerificationToken:
def test_verification_token_creation(self):
token = LiteLLM_VerificationToken(
token="sk-test123",
key_name="Test Key",
user_id="user-123",
team_id="team-123",
max_budget=100.0,
spend=25.0,
models=["gpt-4"],
blocked=True,
allowed_routes=["/chat/completions"],
)
assert token.token == "sk-test123"
assert token.key_name == "Test Key"
assert token.user_id == "user-123"
assert token.team_id == "team-123"
assert token.blocked is True
assert token.models == ["gpt-4"]
assert token.allowed_routes == ["/chat/completions"]
def test_expires_accepts_string_and_datetime(self):
as_str = LiteLLM_VerificationToken(token="t1", expires="2024-12-31T23:59:59Z")
as_dt = LiteLLM_VerificationToken(token="t2", expires=datetime.utcnow())
assert as_str.expires == "2024-12-31T23:59:59Z"
assert isinstance(as_dt.expires, datetime)
def test_deleted_verification_token(self):
deleted = LiteLLM_DeletedVerificationToken(
token="t1",
deleted_by="admin",
deleted_at=datetime.utcnow(),
)
assert deleted.deleted_by == "admin"
assert deleted.deleted_at is not None
assert deleted.token == "t1"
class TestConfigTable:
def test_config_creation(self):
cfg = LiteLLM_Config(param_name="general_settings", param_value={"k": "v"})
assert cfg.param_name == "general_settings"
assert cfg.param_value == {"k": "v"}
class TestSkillsTable:
def test_skills_creation(self):
skill = LiteLLM_SkillsTable(
skill_id="s1",
display_title="My Skill",
source="custom",
file_content=b"zipbytes",
file_name="skill.zip",
)
assert skill.skill_id == "s1"
assert skill.display_title == "My Skill"
assert skill.file_content == b"zipbytes"
def test_skills_defaults(self):
skill = LiteLLM_SkillsTable(skill_id="s2")
assert skill.source == "custom"
assert skill.metadata is None
class TestAccessGroupTable:
def test_access_group_creation(self):
ag = LiteLLM_AccessGroupTable(
access_group_id="ag1",
access_group_name="group-a",
access_model_names=["gpt-4"],
assigned_team_ids=["t1"],
)
assert ag.access_group_id == "ag1"
assert ag.access_model_names == ["gpt-4"]
assert ag.assigned_team_ids == ["t1"]
assert ag.access_agent_ids == []
class TestTagTable:
def test_tag_creation(self):
tag = LiteLLM_TagTable(
tag_name="prod",
models=["gpt-4"],
spend=12.5,
budget_id="b1",
)
assert tag.tag_name == "prod"
assert tag.models == ["gpt-4"]
assert tag.spend == 12.5
def test_tag_set_model_info_coerces_none(self):
tag = LiteLLM_TagTable(tag_name="t", spend=None, models=None)
assert tag.spend == 0.0
assert tag.models == []
class TestEndUserTable:
def test_end_user_creation(self):
eu = LiteLLM_EndUserTable(
user_id="eu1",
blocked=False,
spend=5.0,
allowed_model_region="eu",
default_model="gpt-4",
)
assert eu.user_id == "eu1"
assert eu.blocked is False
assert eu.allowed_model_region == "eu"
assert eu.default_model == "gpt-4"
def test_end_user_spend_coerced_when_none(self):
eu = LiteLLM_EndUserTable(user_id="eu2", blocked=True, spend=None)
assert eu.spend == 0.0
class TestBudgetTableFull:
def test_full_adds_server_managed_fields(self):
now = datetime.now()
budget = LiteLLM_BudgetTableFull(
budget_id="b1", max_budget=10.0, created_at=now, budget_reset_at=now
)
assert budget.created_at == now
assert budget.budget_reset_at == now
assert budget.max_budget == 10.0
def test_full_requires_created_at(self):
with pytest.raises(Exception):
LiteLLM_BudgetTableFull(budget_id="b1")
class TestTeamMemberTable:
def test_tracks_user_within_team(self):
member = LiteLLM_TeamMemberTable(
user_id="u1", team_id="t1", spend=3.0, budget_id="b1", max_budget=5.0
)
assert member.user_id == "u1"
assert member.team_id == "t1"
assert member.spend == 3.0
assert member.max_budget == 5.0
class TestTeamMembership:
def test_safe_get_limits_with_budget_table(self):
membership = LiteLLM_TeamMembership(
user_id="u1",
team_id="t1",
litellm_budget_table=LiteLLM_BudgetTable(rpm_limit=100, tpm_limit=2000),
)
assert membership.safe_get_team_member_rpm_limit() == 100
assert membership.safe_get_team_member_tpm_limit() == 2000
def test_safe_get_limits_without_budget_table(self):
membership = LiteLLM_TeamMembership(user_id="u1", team_id="t1")
assert membership.safe_get_team_member_rpm_limit() is None
assert membership.safe_get_team_member_tpm_limit() is None
def test_full_budget_variant_parsed_for_server_fields(self):
now = datetime.now()
membership = LiteLLM_TeamMembership(
user_id="u1",
team_id="t1",
litellm_budget_table={
"budget_id": "b1",
"rpm_limit": 7,
"created_at": now,
"budget_reset_at": now,
},
)
assert isinstance(membership.litellm_budget_table, LiteLLM_BudgetTableFull)
assert membership.safe_get_team_member_rpm_limit() == 7
class TestMCPServerTable:
def test_mcp_server_defaults(self):
server = LiteLLM_MCPServerTable(server_id="s1", transport="sse")
assert server.server_id == "s1"
assert server.transport == "sse"
assert server.status == "unknown"
assert server.approval_status == "active"
assert server.allow_all_keys is False
assert server.available_on_public_internet is True
assert server.teams == []
assert server.env == {}
def test_mcp_server_requires_transport(self):
with pytest.raises(Exception):
LiteLLM_MCPServerTable(server_id="s1")
class TestSpendLogs:
def test_spend_logs_creation(self):
log = LiteLLM_SpendLogs(
request_id="r1",
api_key="sk-1",
call_type="completion",
startTime=None,
endTime=None,
messages=None,
response=None,
)
assert log.request_id == "r1"
assert log.spend == 0.0
assert log.cache_hit == "False"
def test_error_logs_creation(self):
log = LiteLLM_ErrorLogs(
request_id="r1", startTime=None, endTime=None, status_code="500"
)
assert log.request_id == "r1"
assert log.status_code == "500"
class TestManagedTables:
def test_managed_file_table(self):
table = LiteLLM_ManagedFileTable(
unified_file_id="f1",
model_mappings={"gpt-4": "file-abc"},
flat_model_file_ids=["file-abc"],
)
assert table.unified_file_id == "f1"
assert table.model_mappings == {"gpt-4": "file-abc"}
assert table.flat_model_file_ids == ["file-abc"]
def test_managed_object_table_requires_purpose(self):
with pytest.raises(Exception):
LiteLLM_ManagedObjectTable(
unified_object_id="o1", model_object_id="m1", file_object={}
)
def test_managed_vector_stores_table(self):
table = LiteLLM_ManagedVectorStoresTable(
vector_store_id="vs1",
custom_llm_provider="openai",
vector_store_name=None,
vector_store_description=None,
vector_store_metadata=None,
created_at=None,
updated_at=None,
litellm_credential_name=None,
litellm_params=None,
team_id=None,
user_id=None,
)
assert table.vector_store_id == "vs1"
assert table.custom_llm_provider == "openai"