polukranos / test_api.py
cognivore
Typecheck everything
a68dd7c
Raw
History Blame Contribute Delete
12.6 kB
#!/usr/bin/env python3
"""
Test script for the Multi-Model Chat Backend API with strict typing
"""
import requests
import time
from typing import Dict, Any, List, Optional, Callable
from dataclasses import dataclass
from enum import Enum
from custom_types import (
create_model_id, create_conversation_id, create_user_message,
create_assistant_response
)
# Configuration
API_BASE_URL = "http://localhost:7860"
TEST_MODELS = [
"thoughtcast/outlandish-spiked-lassie-experiment",
"thoughtcast/marketing-spiked-lassie-experiment",
"TinyLlama/TinyLlama-1.1B-Chat-v1.0"
]
class TestStatus(Enum):
"""Test status enumeration."""
PASSED = "passed"
FAILED = "failed"
ERROR = "error"
@dataclass(frozen=True)
class TestResult:
"""Test result with strict typing."""
name: str
status: TestStatus
message: str
details: Optional[Dict[str, Any]] = None
@dataclass(frozen=True)
class ChatTestRequest:
"""Chat test request with validation."""
model_id: str
message: str
conversation_id: str = "test"
max_new_tokens: int = 50
temperature: float = 0.7
top_p: float = 0.9
repetition_penalty: float = 1.1
no_repeat_ngram_size: int = 3
do_sample: bool = True
def to_payload(self) -> Dict[str, Any]:
"""Convert to API payload with validation."""
# Validate inputs using our type system
model_id_wrapper = create_model_id(self.model_id)
conv_id_wrapper = create_conversation_id(self.conversation_id)
message_wrapper = create_user_message(self.message)
return {
"message": message_wrapper.unwrap(),
"conversation_id": conv_id_wrapper.unwrap(),
"model_id": model_id_wrapper.unwrap(),
"max_new_tokens": self.max_new_tokens,
"temperature": self.temperature,
"top_p": self.top_p,
"repetition_penalty": self.repetition_penalty,
"no_repeat_ngram_size": self.no_repeat_ngram_size,
"do_sample": self.do_sample
}
def test_health_check() -> TestResult:
"""Test the health check endpoint."""
print("Testing health check...")
try:
response = requests.get(f"{API_BASE_URL}/health")
if response.status_code == 200:
data = response.json()
print(f"βœ“ Health check passed: {data['status']}")
print(f" Models loaded: {data['models_loaded']}")
print(f" GPU memory used: {data['gpu_memory_used']:.2%}")
print(f" Active conversations: {data['active_conversations']}")
return TestResult(
name="health_check",
status=TestStatus.PASSED,
message="Health check successful",
details=data
)
else:
print(f"βœ— Health check failed: {response.status_code}")
return TestResult(
name="health_check",
status=TestStatus.FAILED,
message=f"HTTP {response.status_code}: {response.text}"
)
except Exception as e:
print(f"βœ— Health check error: {e}")
return TestResult(
name="health_check",
status=TestStatus.ERROR,
message=str(e)
)
def test_list_models() -> TestResult:
"""Test the list models endpoint."""
print("\nTesting list models...")
try:
response = requests.get(f"{API_BASE_URL}/models")
if response.status_code == 200:
data = response.json()
print(f"βœ“ Models endpoint working")
print(f" Available models: {data['available_models']}")
print(f" Loaded models: {data['loaded_models']}")
# Validate model IDs
for model_id in data['available_models']:
try:
_ = create_model_id(model_id)
except ValueError as e:
return TestResult(
name="list_models",
status=TestStatus.FAILED,
message=f"Invalid model ID format: {model_id} - {e}"
)
return TestResult(
name="list_models",
status=TestStatus.PASSED,
message="Models endpoint working",
details=data
)
else:
print(f"βœ— Models endpoint failed: {response.status_code}")
return TestResult(
name="list_models",
status=TestStatus.FAILED,
message=f"HTTP {response.status_code}: {response.text}"
)
except Exception as e:
print(f"βœ— Models endpoint error: {e}")
return TestResult(
name="list_models",
status=TestStatus.ERROR,
message=str(e)
)
def test_chat(model_id: str, message: str, conversation_id: str = "test") -> TestResult:
"""Test the chat endpoint with a specific model."""
print(f"\nTesting chat with {model_id}...")
try:
# Create and validate chat request
chat_request = ChatTestRequest(
model_id=model_id,
message=message,
conversation_id=conversation_id
)
payload = chat_request.to_payload()
response = requests.post(f"{API_BASE_URL}/chat", json=payload)
if response.status_code == 200:
data = response.json()
# Validate response
try:
_ = create_model_id(data['model_id'])
_ = create_conversation_id(data['conversation_id'])
_ = create_assistant_response(data['response'])
except ValueError as e:
return TestResult(
name=f"chat_{model_id.replace('/', '_')}",
status=TestStatus.FAILED,
message=f"Invalid response format: {e}"
)
print(f"βœ“ Chat successful with {model_id}")
print(f" Response: {data['response'][:100]}...")
print(f" Tokens used: {data['tokens_used']}")
return TestResult(
name=f"chat_{model_id.replace('/', '_')}",
status=TestStatus.PASSED,
message=f"Chat successful with {model_id}",
details=data
)
else:
print(f"βœ— Chat failed with {model_id}: {response.status_code}")
print(f" Error: {response.text}")
return TestResult(
name=f"chat_{model_id.replace('/', '_')}",
status=TestStatus.FAILED,
message=f"HTTP {response.status_code}: {response.text}"
)
except ValueError as e:
print(f"βœ— Chat validation error with {model_id}: {e}")
return TestResult(
name=f"chat_{model_id.replace('/', '_')}",
status=TestStatus.ERROR,
message=f"Validation error: {e}"
)
except Exception as e:
print(f"βœ— Chat error with {model_id}: {e}")
return TestResult(
name=f"chat_{model_id.replace('/', '_')}",
status=TestStatus.ERROR,
message=str(e)
)
def test_conversation_management() -> TestResult:
"""Test conversation management endpoints."""
print("\nTesting conversation management...")
try:
# List conversations
response = requests.get(f"{API_BASE_URL}/conversations")
if response.status_code == 200:
data = response.json()
print(f"βœ“ List conversations working")
print(f" Active conversations: {len(data['conversations'])}")
# Get specific conversation
test_conv_id = "test"
try:
_ = create_conversation_id(test_conv_id)
except ValueError as e:
return TestResult(
name="conversation_management",
status=TestStatus.ERROR,
message=f"Invalid test conversation ID: {e}"
)
response = requests.get(f"{API_BASE_URL}/conversations/{test_conv_id}")
if response.status_code == 200:
data = response.json()
print(f"βœ“ Get conversation working")
print(f" Messages in conversation: {data['message_count']}")
# Export conversation
response = requests.post(f"{API_BASE_URL}/conversations/{test_conv_id}/export")
if response.status_code == 200:
data = response.json()
print(f"βœ“ Export conversation working")
print(f" Exported {len(data['messages'])} messages")
return TestResult(
name="conversation_management",
status=TestStatus.PASSED,
message="Conversation management working"
)
except Exception as e:
print(f"βœ— Conversation management error: {e}")
return TestResult(
name="conversation_management",
status=TestStatus.ERROR,
message=str(e)
)
def test_model_loading() -> TestResult:
"""Test model loading and unloading."""
print("\nTesting model loading/unloading...")
try:
# Try to load a model
model_id = "TinyLlama/TinyLlama-1.1B-Chat-v1.0"
# Validate model ID
try:
_ = create_model_id(model_id)
except ValueError as e:
return TestResult(
name="model_loading",
status=TestStatus.ERROR,
message=f"Invalid model ID: {e}"
)
response = requests.post(f"{API_BASE_URL}/models/{model_id}/load")
if response.status_code == 200:
print(f"βœ“ Model loading working for {model_id}")
return TestResult(
name="model_loading",
status=TestStatus.PASSED,
message=f"Model loading successful for {model_id}"
)
else:
print(f"βœ— Model loading failed for {model_id}: {response.status_code}")
return TestResult(
name="model_loading",
status=TestStatus.FAILED,
message=f"HTTP {response.status_code}: {response.text}"
)
except Exception as e:
print(f"βœ— Model loading error: {e}")
return TestResult(
name="model_loading",
status=TestStatus.ERROR,
message=str(e)
)
def run_comprehensive_test() -> bool:
"""Run all tests with strict typing."""
print("πŸš€ Starting comprehensive API tests...")
print("=" * 60)
# Wait for service to be ready
print("Waiting for service to be ready...")
for _ in range(30): # Wait up to 30 seconds
health_result = test_health_check()
if health_result.status == TestStatus.PASSED:
break
time.sleep(1)
else:
print("βœ— Service not ready after 30 seconds")
return False
# Define test functions
test_functions: List[Callable[[], TestResult]] = [
test_list_models,
lambda: test_chat("thoughtcast/outlandish-spiked-lassie-experiment", "Hello, how are you?"),
lambda: test_chat("thoughtcast/marketing-spiked-lassie-experiment", "Tell me about your marketing strategy"),
lambda: test_chat("TinyLlama/TinyLlama-1.1B-Chat-v1.0", "What is machine learning?"),
test_conversation_management,
test_model_loading,
]
# Run tests and collect results
results: List[TestResult] = []
for test_func in test_functions:
result = test_func()
results.append(result)
# Analyze results
passed = sum(1 for r in results if r.status == TestStatus.PASSED)
failed = sum(1 for r in results if r.status == TestStatus.FAILED)
errors = sum(1 for r in results if r.status == TestStatus.ERROR)
print("\n" + "=" * 60)
print(f"🎯 Test Results: {passed}/{len(results)} tests passed")
print(f" Passed: {passed}")
print(f" Failed: {failed}")
print(f" Errors: {errors}")
# Print failed/error tests
if failed > 0 or errors > 0:
print("\n❌ Failed/Error Tests:")
for result in results:
if result.status != TestStatus.PASSED:
print(f" {result.name}: {result.status.value} - {result.message}")
if passed == len(results):
print("πŸŽ‰ All tests passed! The API is working correctly.")
return True
else:
print("❌ Some tests failed. Please check the logs.")
return False
if __name__ == "__main__":
success = run_comprehensive_test()
exit(0 if success else 1)