cai/tests/test_pricing.py

435 lines
16 KiB
Python

#!/usr/bin/env python3
from __future__ import annotations
import sys
import os
import tempfile
import json
from pathlib import Path
from unittest.mock import patch, MagicMock
# Try to import pytest, but make it optional
try:
import pytest
PYTEST_AVAILABLE = True
except ImportError:
PYTEST_AVAILABLE = False
# Create a dummy fixture decorator for when pytest is not available
def pytest_fixture(*args, **kwargs):
def decorator(func):
return func
return decorator
pytest = type('pytest', (), {'fixture': pytest_fixture})
# Add the src directory to the Python path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..', 'src'))
from cai.util import COST_TRACKER, calculate_model_cost
if PYTEST_AVAILABLE:
@pytest.fixture(autouse=True)
def setup_test():
"""Clear the pricing cache before each test"""
COST_TRACKER.model_pricing_cache.clear()
yield
COST_TRACKER.model_pricing_cache.clear()
else:
def setup_test():
"""Clear the pricing cache before each test"""
COST_TRACKER.model_pricing_cache.clear()
def test_local_models_return_zero_cost():
"""Test that local models return zero cost"""
if not PYTEST_AVAILABLE:
setup_test()
local_models = [
"qwen3:14b",
"qwen3:32b",
"qwen2.5:14b",
"qwen2.5:7b",
"qwen2.5:72b",
"llama3.1:8b",
"llama3.1:70b",
"mistral:7b",
"mistral:latest",
"codellama:13b",
"llama3.1",
"ollama/qwen2.5",
"deepseek-coder:6.7b",
"phi3:mini",
"gemma:7b",
"vicuna:13b",
"alpaca:7b",
"orca-mini:3b",
"neural-chat:7b",
"starling-lm:7b",
"zephyr:7b",
"openchat:7b",
"wizard-coder:15b",
"sqlcoder:7b",
"magicoder:7b",
"dolphin-mixtral:8x7b",
"nous-hermes2:10.7b",
"yi:34b",
"qwq:32b",
"alias01:14b",
"alias01:14b-ctx-32000",
"alias00:14b"
]
print("\n" + "="*80)
print("LOCAL MODELS PRICING TEST")
print("="*80)
for model in local_models:
with patch('requests.get') as mock_get:
# Mock LiteLLM API to return empty response (model not found)
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {} # Empty response, model not found
mock_get.return_value = mock_response
pricing = COST_TRACKER.get_model_pricing(model)
cost = calculate_model_cost(model, 100, 50)
# Show pricing information
print(f"Model: {model:<25} | Pricing: {pricing} | Cost (100/50 tokens): ${cost:.6f}")
if PYTEST_AVAILABLE:
assert pricing == (0.0, 0.0), f"Model {model} should have zero pricing, got {pricing}"
assert cost == 0.0, f"Model {model} should have zero cost, got {cost}"
else:
if pricing != (0.0, 0.0):
raise AssertionError(f"Model {model} should have zero pricing, got {pricing}")
if cost != 0.0:
raise AssertionError(f"Model {model} should have zero cost, got {cost}")
print("="*80)
def test_paid_models_return_nonzero_cost():
"""Test that known paid models return non-zero cost"""
if not PYTEST_AVAILABLE:
setup_test()
paid_models_with_expected_pricing = {
"gpt-4": {"input_cost_per_token": 0.00003, "output_cost_per_token": 0.00006},
"gpt-4o": {"input_cost_per_token": 0.0000025, "output_cost_per_token": 0.00001},
"claude-3-sonnet-20240229": {"input_cost_per_token": 0.000003, "output_cost_per_token": 0.000015},
"claude-3-5-sonnet-20241022": {"input_cost_per_token": 0.000003, "output_cost_per_token": 0.000015}
}
print("\n" + "="*80)
print("PAID MODELS PRICING TEST")
print("="*80)
for model, expected_pricing in paid_models_with_expected_pricing.items():
with patch('requests.get') as mock_get:
# Mock LiteLLM API to return pricing for paid models
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
model: expected_pricing
}
mock_get.return_value = mock_response
pricing = COST_TRACKER.get_model_pricing(model)
cost = calculate_model_cost(model, 100, 50)
# Show pricing information
print(f"Model: {model:<30} | Pricing: {pricing} | Cost (100/50 tokens): ${cost:.6f}")
if PYTEST_AVAILABLE:
assert pricing[0] > 0 or pricing[1] > 0, f"Model {model} should have non-zero pricing, got {pricing}"
assert cost > 0, f"Model {model} should have non-zero cost, got {cost}"
else:
if not (pricing[0] > 0 or pricing[1] > 0):
raise AssertionError(f"Model {model} should have non-zero pricing, got {pricing}")
if not (cost > 0):
raise AssertionError(f"Model {model} should have non-zero cost, got {cost}")
print("="*80)
def test_private_model_alias0_with_pricing_json():
"""Test that alias0 works correctly when defined in pricing.json"""
if not PYTEST_AVAILABLE:
setup_test()
# Create a pricing.json with alias0 configuration
pricing_config = {
"alias0": {
"max_tokens": 128000,
"max_input_tokens": 200000,
"max_output_tokens": 128000,
"input_cost_per_token": 5e-06,
"output_cost_per_token": 5e-05,
"litellm_provider": "openai",
"mode": "chat",
"supports_function_calling": True,
"supports_vision": True
}
}
print("\n" + "="*80)
print("PRIVATE MODEL (alias0) PRICING TEST")
print("="*80)
# Mock the file reading to simulate pricing.json with alias0
with patch('pathlib.Path') as mock_path:
mock_path_instance = MagicMock()
mock_path_instance.exists.return_value = True
mock_path.return_value = mock_path_instance
with patch('builtins.open', create=True) as mock_open:
mock_file = MagicMock()
mock_file.__enter__.return_value = mock_file
mock_file.read.return_value = json.dumps(pricing_config)
mock_open.return_value = mock_file
# Mock json.load to return our config
with patch('json.load', return_value=pricing_config):
pricing = COST_TRACKER.get_model_pricing("alias0")
cost = calculate_model_cost("alias0", 100, 50)
expected_pricing = (5e-06, 5e-05)
expected_cost = 100 * 5e-06 + 50 * 5e-05 # 0.0030
# Show pricing information
print(f"Model: alias0 | Pricing: {pricing} | Cost (100/50 tokens): ${cost:.6f}")
print(f"Expected pricing: {expected_pricing} | Expected cost: ${expected_cost:.6f}")
if PYTEST_AVAILABLE:
assert pricing == expected_pricing, f"alias0 should have pricing {expected_pricing}, got {pricing}"
assert abs(cost - expected_cost) < 1e-10, f"alias0 should have cost {expected_cost}, got {cost}"
else:
if pricing != expected_pricing:
raise AssertionError(f"alias0 should have pricing {expected_pricing}, got {pricing}")
if abs(cost - expected_cost) >= 1e-10:
raise AssertionError(f"alias0 should have cost {expected_cost}, got {cost}")
print("="*80)
def test_reset_cost_for_local_model():
"""Test the reset_cost_for_local_model function"""
if not PYTEST_AVAILABLE:
setup_test()
print("\n" + "="*80)
print("RESET COST FOR LOCAL MODEL TEST")
print("="*80)
# Test with a free model
with patch('requests.get') as mock_get:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {} # Model not found, will return (0.0, 0.0)
mock_get.return_value = mock_response
result = COST_TRACKER.reset_cost_for_local_model("qwen3:14b")
pricing = COST_TRACKER.get_model_pricing("qwen3:14b")
print(f"Model: qwen3:14b (free) | Pricing: {pricing} | Reset result: {result}")
if PYTEST_AVAILABLE:
assert result == True, "qwen3:14b should be identified as a free model"
else:
if result != True:
raise AssertionError("qwen3:14b should be identified as a free model")
# Test with a paid model
with patch('requests.get') as mock_get:
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"gpt-4": {
"input_cost_per_token": 0.00003,
"output_cost_per_token": 0.00006
}
}
mock_get.return_value = mock_response
result = COST_TRACKER.reset_cost_for_local_model("gpt-4")
pricing = COST_TRACKER.get_model_pricing("gpt-4")
print(f"Model: gpt-4 (paid) | Pricing: {pricing} | Reset result: {result}")
if PYTEST_AVAILABLE:
assert result == False, "gpt-4 should not be identified as a free model"
else:
if result != False:
raise AssertionError("gpt-4 should not be identified as a free model")
print("="*80)
def test_model_not_found_anywhere_returns_zero():
"""Test that models not found anywhere return zero cost"""
if not PYTEST_AVAILABLE:
setup_test()
unknown_model = "unknown-model-12345"
print("\n" + "="*80)
print("UNKNOWN MODEL PRICING TEST")
print("="*80)
with patch('pathlib.Path') as mock_path:
mock_path_instance = MagicMock()
mock_path_instance.exists.return_value = False # No pricing.json
mock_path.return_value = mock_path_instance
with patch('requests.get') as mock_get:
# Mock LiteLLM API to return empty response
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {} # Model not found
mock_get.return_value = mock_response
pricing = COST_TRACKER.get_model_pricing(unknown_model)
cost = calculate_model_cost(unknown_model, 100, 50)
# Show pricing information
print(f"Model: {unknown_model:<20} | Pricing: {pricing} | Cost (100/50 tokens): ${cost:.6f}")
if PYTEST_AVAILABLE:
assert pricing == (0.0, 0.0), f"Unknown model should have zero pricing, got {pricing}"
assert cost == 0.0, f"Unknown model should have zero cost, got {cost}"
else:
if pricing != (0.0, 0.0):
raise AssertionError(f"Unknown model should have zero pricing, got {pricing}")
if cost != 0.0:
raise AssertionError(f"Unknown model should have zero cost, got {cost}")
print("="*80)
def test_model_not_in_pricing_json_falls_back_to_litellm():
"""Test that models not in pricing.json fall back to LiteLLM API"""
if not PYTEST_AVAILABLE:
setup_test()
# Create a pricing.json with only alias0
pricing_config = {
"alias0": {
"input_cost_per_token": 5e-06,
"output_cost_per_token": 5e-05
}
}
print("\n" + "="*80)
print("PRICING.JSON vs LITELLM FALLBACK TEST")
print("="*80)
with patch('pathlib.Path') as mock_path:
mock_path_instance = MagicMock()
mock_path_instance.exists.return_value = True
mock_path.return_value = mock_path_instance
with patch('builtins.open', create=True) as mock_open:
mock_file = MagicMock()
mock_file.__enter__.return_value = mock_file
mock_open.return_value = mock_file
with patch('json.load', return_value=pricing_config):
with patch('requests.get') as mock_get:
# Mock LiteLLM API response for gpt-4
mock_response = MagicMock()
mock_response.status_code = 200
mock_response.json.return_value = {
"gpt-4": {
"input_cost_per_token": 0.00003,
"output_cost_per_token": 0.00006
}
}
mock_get.return_value = mock_response
# Test that gpt-4 (not in pricing.json) uses LiteLLM
pricing = COST_TRACKER.get_model_pricing("gpt-4")
expected_gpt4_pricing = (0.00003, 0.00006)
cost_gpt4 = calculate_model_cost("gpt-4", 100, 50)
# Test that alias0 (in pricing.json) uses local pricing
pricing_alias0 = COST_TRACKER.get_model_pricing("alias0")
expected_alias0_pricing = (5e-06, 5e-05)
cost_alias0 = calculate_model_cost("alias0", 100, 50)
# Show pricing information
print(f"Model: gpt-4 (from LiteLLM) | Pricing: {pricing} | Cost (100/50 tokens): ${cost_gpt4:.6f}")
print(f"Model: alias0 (from pricing.json) | Pricing: {pricing_alias0} | Cost (100/50 tokens): ${cost_alias0:.6f}")
if PYTEST_AVAILABLE:
assert pricing == expected_gpt4_pricing, f"gpt-4 should use LiteLLM pricing, got {pricing}"
assert pricing_alias0 == expected_alias0_pricing, f"alias0 should use local pricing, got {pricing_alias0}"
else:
if pricing != expected_gpt4_pricing:
raise AssertionError(f"gpt-4 should use LiteLLM pricing, got {pricing}")
if pricing_alias0 != expected_alias0_pricing:
raise AssertionError(f"alias0 should use local pricing, got {pricing_alias0}")
print("="*80)
# Fallback for direct execution
def run_all_tests():
"""Run all tests and report results - for direct execution"""
print("Running pricing tests...")
print("=" * 50)
print("NOTE: This will show detailed pricing information for all models tested")
print("=" * 50)
test_functions = [
test_local_models_return_zero_cost,
test_paid_models_return_nonzero_cost,
test_private_model_alias0_with_pricing_json,
test_reset_cost_for_local_model,
test_model_not_found_anywhere_returns_zero,
test_model_not_in_pricing_json_falls_back_to_litellm
]
passed = 0
failed = 0
# Setup
COST_TRACKER.model_pricing_cache.clear()
for test_func in test_functions:
try:
COST_TRACKER.model_pricing_cache.clear() # Clear before each test
print(f"\n🧪 Running: {test_func.__name__}")
test_func()
print(f"✅ PASSED: {test_func.__name__}")
passed += 1
except Exception as e:
print(f"❌ FAILED: {test_func.__name__}")
print(f" - Exception: {e}")
failed += 1
print()
print("=" * 80)
print(f"FINAL TEST RESULTS: {passed} passed, {failed} failed")
print("=" * 80)
if failed == 0:
print("🎉 All tests passed! ✅")
print("\nSUMMARY:")
print("- Local models correctly return zero cost")
print("- Paid models correctly return non-zero cost")
print("- Private models (alias0) work with pricing.json")
print("- Reset cost function works correctly")
print("- Unknown models default to zero cost")
print("- Fallback from pricing.json to LiteLLM works")
return True
else:
print("💥 Some tests failed! ❌")
return False
if __name__ == '__main__':
success = run_all_tests()
sys.exit(0 if success else 1)