feat: complete production-quality Model Router AI traffic control platform
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
import time
|
||||
import asyncio
|
||||
from typing import List, Optional, Tuple, Dict, Any
|
||||
from app.models.schemas import ProviderResponse, ModelMetadata
|
||||
from app.providers.registry import provider_registry
|
||||
from app.config.settings import get_settings
|
||||
|
||||
settings = get_settings()
|
||||
|
||||
RETRYABLE_ERRORS = [
|
||||
"timeout",
|
||||
"connection error",
|
||||
"rate limit",
|
||||
"429",
|
||||
"503",
|
||||
"service unavailable",
|
||||
"econnreset",
|
||||
]
|
||||
|
||||
|
||||
def is_retryable(error_str: Optional[str]) -> bool:
|
||||
if not error_str:
|
||||
return False
|
||||
low = error_str.lower()
|
||||
return any(keyword in low for keyword in RETRYABLE_ERRORS)
|
||||
|
||||
|
||||
async def execute_with_fallback(
|
||||
prompt: str,
|
||||
selected_model_id: str,
|
||||
selected_provider_id: str,
|
||||
all_models: List[ModelMetadata],
|
||||
system_prompt: Optional[str] = None,
|
||||
temperature: float = 0.7,
|
||||
max_retries: int = 1,
|
||||
) -> Tuple[ProviderResponse, bool, Optional[str], Optional[str]]:
|
||||
"""
|
||||
Executes model generation with configurable retries and fallback hierarchy.
|
||||
Returns: (ProviderResponse, fallback_used, original_model, fallback_reason)
|
||||
"""
|
||||
current_model_id = selected_model_id
|
||||
current_provider_id = selected_provider_id
|
||||
fallback_used = False
|
||||
original_model = selected_model_id
|
||||
fallback_reason = None
|
||||
|
||||
# Step 1: Attempt primary model with retries
|
||||
provider = provider_registry.get_provider(current_provider_id)
|
||||
if not provider:
|
||||
provider = provider_registry.get_provider("mock")
|
||||
current_provider_id = "mock"
|
||||
current_model_id = "mock-balanced"
|
||||
fallback_used = True
|
||||
fallback_reason = f"Provider '{selected_provider_id}' is not loaded. Fell back to Mock."
|
||||
|
||||
for attempt in range(max_retries + 1):
|
||||
resp = await provider.generate(
|
||||
prompt=prompt,
|
||||
model_id=current_model_id,
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
)
|
||||
|
||||
if not resp.error and resp.finish_reason != "error":
|
||||
return resp, fallback_used, original_model if fallback_used else None, fallback_reason
|
||||
|
||||
# If non-retryable error or last attempt, prepare for fallback
|
||||
if not is_retryable(resp.error) or attempt == max_retries:
|
||||
fallback_reason = resp.error or "Unknown primary model execution error."
|
||||
break
|
||||
|
||||
await asyncio.sleep(0.5 * (attempt + 1))
|
||||
|
||||
# Step 2: Determine Fallback Candidate
|
||||
fallback_used = True
|
||||
# Priority: Mock model of same or balanced tier, or active local Ollama
|
||||
candidate_fallbacks = [
|
||||
m for m in all_models
|
||||
if m.id != selected_model_id and m.is_active and (m.provider == "mock" or m.type == "LOCAL")
|
||||
]
|
||||
if not candidate_fallbacks:
|
||||
candidate_fallbacks = [m for m in all_models if m.id != selected_model_id and m.is_active]
|
||||
|
||||
fallback_target = candidate_fallbacks[0] if candidate_fallbacks else None
|
||||
|
||||
if fallback_target:
|
||||
fb_provider = provider_registry.get_provider(fallback_target.provider) or provider_registry.get_provider("mock")
|
||||
fb_resp = await fb_provider.generate(
|
||||
prompt=prompt,
|
||||
model_id=fallback_target.id,
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
)
|
||||
if not fb_resp.error:
|
||||
return fb_resp, True, original_model, f"Primary model failed: {fallback_reason} -> Fallback to {fallback_target.id}"
|
||||
|
||||
# Step 3: Emergency Hard Mock fallback
|
||||
emergency_mock = provider_registry.get_provider("mock")
|
||||
emergency_resp = await emergency_mock.generate(
|
||||
prompt=prompt,
|
||||
model_id="mock-balanced",
|
||||
system_prompt=system_prompt,
|
||||
temperature=temperature,
|
||||
)
|
||||
return emergency_resp, True, original_model, f"Emergency fallback triggered due to: {fallback_reason}"
|
||||
Reference in New Issue
Block a user