feat: complete production-quality Model Router AI traffic control platform
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import pytest
|
||||
from app.analyzer.heuristics import analyze_request_heuristics, estimate_tokens
|
||||
from app.models.schemas import TaskType, PriorityLevel
|
||||
|
||||
|
||||
def test_token_estimation():
|
||||
text = "Hello world from Model Router platform!"
|
||||
tokens = estimate_tokens(text)
|
||||
assert tokens > 0
|
||||
|
||||
|
||||
def test_heuristic_classification_coding():
|
||||
prompt = "Write a python function to calculate fibonacci sequence using dynamic programming"
|
||||
analysis = analyze_request_heuristics(prompt)
|
||||
assert analysis.task_type == TaskType.CODING
|
||||
assert analysis.coding_required is True
|
||||
assert analysis.complexity >= 0.40
|
||||
|
||||
|
||||
def test_heuristic_classification_debugging_complexity():
|
||||
prompt = "Debug this distributed async deadlock issue in the microservices cluster and explain the root cause"
|
||||
analysis = analyze_request_heuristics(prompt)
|
||||
assert analysis.task_type == TaskType.DEBUGGING
|
||||
assert analysis.reasoning_required is True
|
||||
assert analysis.coding_required is True
|
||||
assert analysis.complexity_label == PriorityLevel.HIGH
|
||||
assert analysis.complexity >= 0.75
|
||||
|
||||
|
||||
def test_heuristic_classification_summarization():
|
||||
prompt = "Provide a summary and key bullet points of this meeting transcript"
|
||||
analysis = analyze_request_heuristics(prompt)
|
||||
assert analysis.task_type == TaskType.SUMMARIZATION
|
||||
assert analysis.complexity_label in (PriorityLevel.LOW, PriorityLevel.MEDIUM)
|
||||
|
||||
|
||||
def test_heuristic_classification_math():
|
||||
prompt = "Calculate the derivative and integral of matrix eigenvalue differential equation"
|
||||
analysis = analyze_request_heuristics(prompt)
|
||||
assert analysis.task_type == TaskType.MATH
|
||||
assert analysis.reasoning_required is True
|
||||
@@ -0,0 +1,66 @@
|
||||
import pytest
|
||||
from httpx import AsyncClient, ASGITransport
|
||||
from main import app
|
||||
from app.storage.database import init_db
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_db():
|
||||
await init_db()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_health():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
res = await client.get("/api/health")
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert data["status"] == "healthy"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_route_only():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
payload = {"prompt": "Write a Python script to benchmark models", "policy": "balanced"}
|
||||
res = await client.post("/api/route", json=payload)
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert "decision" in data
|
||||
assert "analysis" in data
|
||||
assert data["analysis"]["task_type"] == "CODING"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_generate_end_to_end():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
payload = {"prompt": "Debug this memory leak in python subprocess", "policy": "balanced"}
|
||||
res = await client.post("/api/generate", json=payload)
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert "response" in data
|
||||
assert "metrics" in data
|
||||
assert len(data["response"]["content"]) > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_models_list():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
res = await client.get("/api/models")
|
||||
assert res.status_code == 200
|
||||
models = res.json()
|
||||
assert len(models) >= 3
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_analytics():
|
||||
transport = ASGITransport(app=app)
|
||||
async with AsyncClient(transport=transport, base_url="http://test") as client:
|
||||
res = await client.get("/api/analytics")
|
||||
assert res.status_code == 200
|
||||
data = res.json()
|
||||
assert "total_requests" in data
|
||||
assert "savings" in data
|
||||
@@ -0,0 +1,21 @@
|
||||
import pytest
|
||||
from app.providers.mock_provider import MockProvider
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mock_provider_generate():
|
||||
provider = MockProvider()
|
||||
resp = await provider.generate("Write a quick Python sort function", model_id="mock-fast")
|
||||
assert resp.is_mock is True
|
||||
assert "DEMO/MOCK" in resp.content
|
||||
assert resp.input_tokens > 0
|
||||
assert resp.output_tokens > 0
|
||||
assert resp.provider_latency_ms > 0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_mock_provider_health():
|
||||
provider = MockProvider()
|
||||
health = await provider.check_health()
|
||||
assert health["status"] == "CONNECTED"
|
||||
assert "mock-fast" in health["models_available"]
|
||||
@@ -0,0 +1,78 @@
|
||||
import pytest
|
||||
from app.models.schemas import ModelMetadata, ModelTier, RequestAnalysis, TaskType, PriorityLevel
|
||||
from app.router.scoring import filter_candidate_models, compute_candidate_score
|
||||
from app.router.engine import route_request
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_models():
|
||||
return [
|
||||
ModelMetadata(
|
||||
id="mock-fast",
|
||||
name="Mock Fast",
|
||||
provider="mock",
|
||||
tier=ModelTier.FAST,
|
||||
context_window=8192,
|
||||
supports_coding=True,
|
||||
supports_reasoning=False,
|
||||
quality_score=0.75,
|
||||
speed_score=0.98,
|
||||
cost_per_input_token=0.0,
|
||||
cost_per_output_token=0.0,
|
||||
),
|
||||
ModelMetadata(
|
||||
id="mock-power",
|
||||
name="Mock Power",
|
||||
provider="mock",
|
||||
tier=ModelTier.POWER,
|
||||
context_window=128000,
|
||||
supports_coding=True,
|
||||
supports_reasoning=True,
|
||||
quality_score=0.98,
|
||||
speed_score=0.60,
|
||||
cost_per_input_token=0.000005,
|
||||
cost_per_output_token=0.000015,
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def test_candidate_filtering_context_overflow(sample_models):
|
||||
large_analysis = RequestAnalysis(
|
||||
task_type=TaskType.LONG_CONTEXT,
|
||||
complexity=0.7,
|
||||
context_size=15000, # exceeds mock-fast 8192
|
||||
)
|
||||
eligible, rejected = filter_candidate_models(sample_models, large_analysis)
|
||||
assert len(eligible) == 1
|
||||
assert eligible[0].id == "mock-power"
|
||||
assert "mock-fast" in rejected
|
||||
|
||||
|
||||
def test_scoring_weights(sample_models):
|
||||
analysis = RequestAnalysis(
|
||||
task_type=TaskType.CODING,
|
||||
complexity=0.3,
|
||||
latency_priority=PriorityLevel.HIGH,
|
||||
cost_sensitivity=PriorityLevel.HIGH,
|
||||
)
|
||||
weights = {"quality_weight": 0.1, "cost_weight": 0.5, "speed_weight": 0.4, "capability_weight": 0.0, "reliability_weight": 0.0}
|
||||
score_fast = compute_candidate_score(sample_models[0], analysis, weights)
|
||||
score_power = compute_candidate_score(sample_models[1], analysis, weights)
|
||||
assert score_fast.overall_score > score_power.overall_score
|
||||
|
||||
|
||||
def test_routing_decision_explainability(sample_models):
|
||||
analysis = RequestAnalysis(
|
||||
task_type=TaskType.DEBUGGING,
|
||||
complexity=0.85,
|
||||
complexity_label=PriorityLevel.HIGH,
|
||||
reasoning_required=True,
|
||||
coding_required=True,
|
||||
quality_requirement=PriorityLevel.HIGH,
|
||||
)
|
||||
weights = {"quality_weight": 0.6, "cost_weight": 0.1, "speed_weight": 0.1, "capability_weight": 0.1, "reliability_weight": 0.1}
|
||||
decision = route_request(analysis, sample_models, weights, policy_name="highest_quality")
|
||||
assert decision.selected_model == "mock-power"
|
||||
assert decision.confidence > 0.70
|
||||
assert len(decision.reasons) > 0
|
||||
assert any("DEBUGGING" in r for r in decision.reasons)
|
||||
Reference in New Issue
Block a user