use std::collections::{BTreeMap, HashMap};
use std::sync::{atomic::Ordering, Arc, Mutex, RwLock};
use std::time::{Duration, Instant};
#[derive(Debug, Clone)]
pub struct ManagerConfig {
pub max_concurrent_models: usize,
pub model_timeout: Duration,
pub enable_auto_scaling: bool,
}
#[derive(Debug, Clone)]
pub struct ModelInfo {
pub name: String,
pub version: String,
pub model_type: String, pub endpoint: String,
pub capabilities: Vec<String>,
}
pub struct ModelRegistry {
config: ModelRegistryConfig,
registered_models: Arc<RwLock<HashMap<String, ModelInfo>>>,
health_status: Arc<RwLock<HashMap<String, bool>>>,
}
#[derive(Debug, Clone)]
pub struct ModelRegistryConfig {
pub auto_discovery: bool,
pub health_check_interval: Duration,
}
pub struct ModelRouter {
config: RouterConfig,
model_metrics: Arc<RwLock<HashMap<String, ModelMetrics>>>,
route_cache: Arc<RwLock<HashMap<String, (String, Instant)>>>,
}
#[derive(Debug, Clone)]
pub struct RouterConfig {
pub load_balancing: LoadBalancingStrategy,
pub fallback_enabled: bool,
pub route_cache_ttl: Duration,
}
#[derive(Debug, Clone, PartialEq)]
pub enum LoadBalancingStrategy {
RoundRobin,
LeastConnections,
WeightedRoundRobin,
LatencyBased,
}
#[derive(Debug, Clone)]
pub struct ModelMetrics {
pub latency: Duration,
pub throughput: f64,
pub error_rate: f64,
pub load: f64,
}
pub struct ModelManager {
runtime: Arc<tokio::runtime::Runtime>,
config: ManagerConfig,
registry: ModelRegistry,
router: ModelRouter,
active_models: Arc<RwLock<HashMap<String, ModelHandle>>>,
model_handles: Arc<Mutex<Vec<ModelHandle>>>,
}
#[derive(Debug, Clone)]
pub struct ModelHandle {
pub model_name: String,
pub handle_id: String,
pub load_time: Instant,
pub last_used: Instant,
}
impl ModelRegistry {
pub fn new(config: ModelRegistryConfig) -> Result<Self, String> {
Ok(ModelRegistry {
config: config.clone(),
registered_models: Arc::new(RwLock::new(HashMap::new())),
health_status: Arc::new(RwLock::new(HashMap::new())),
})
}
pub fn register_model(&mut self, model_info: ModelInfo) -> Result<(), String> {
let mut models = self.registered_models.write().unwrap();
models.insert(model_info.name.clone(), model_info.clone());
let mut health = self.health_status.write().unwrap();
health.insert(model_info.name.clone(), true);
Ok(())
}
pub fn discover_models(&self) -> Result<Vec<String>, String> {
let models: _ = self.registered_models.read().unwrap();
Ok(models.keys().cloned().collect())
}
pub fn get_model_info(&self, model_name: &str) -> Option<ModelInfo> {
let models: _ = self.registered_models.read().unwrap();
models.get(model_name).cloned()
}
pub fn is_healthy(&self, model_name: &str) -> bool {
let health: _ = self.health_status.read().unwrap();
health.get(model_name).copied().unwrap_or(false)
}
pub fn mark_unhealthy(&self, model_name: &str) {
let mut health = self.health_status.write().unwrap();
health.insert(model_name.to_string(), false);
}
pub fn mark_healthy(&self, model_name: &str) {
let mut health = self.health_status.write().unwrap();
health.insert(model_name.to_string(), true);
}
}
impl ModelRouter {
pub fn new(config: RouterConfig) -> Result<Self, String> {
Ok(ModelRouter {
config: config.clone(),
model_metrics: Arc::new(RwLock::new(HashMap::new())),
route_cache: Arc::new(RwLock::new(HashMap::new())),
})
}
pub fn add_model(&self, model_name: String, metrics: ModelMetrics) {
let mut model_metrics = self.model_metrics.write().unwrap();
model_metrics.insert(model_name, metrics);
}
pub fn route_request(&self, capability: String) -> Result<String, String> {
let cache_key: _ = format!("route:{}", capability);
if let Some((cached_model, cached_time)) = self.route_cache.read().unwrap().get(&cache_key)
{
if cached_time.elapsed() < self.config.route_cache_ttl {
return Ok(cached_model.clone());
}
}
let model_metrics: _ = self.model_metrics.read().unwrap();
let available_models: Vec<_> = model_metrics
.iter()
.filter(|(_, metrics)| metrics.load < 0.9 && metrics.error_rate < 0.1)
.collect();
if available_models.is_empty() {
return Err("No available models".to_string());
}
let model_name: _ = self.select_model(&available_models)?;
{
let mut cache = self.route_cache.write().unwrap();
cache.insert(cache_key, (model_name.clone(), Instant::now()));
}
Ok(model_name)
}
fn select_model(&self, models: &[(&String, &ModelMetrics)]) -> Result<String, String> {
match self.config.load_balancing {
LoadBalancingStrategy::RoundRobin => {
Ok(models[0].0.clone())
}
LoadBalancingStrategy::LeastConnections => {
Ok(models
.iter()
.min_by(|(_, a), (_, b)| {
a.load
.partial_cmp(&b.load)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(name, _)| (*name).clone())
.unwrap())
}
LoadBalancingStrategy::WeightedRoundRobin => {
Ok(models
.iter()
.max_by(|(_, a), (_, b)| {
a.throughput
.partial_cmp(&b.throughput)
.unwrap_or(std::cmp::Ordering::Equal)
})
.map(|(name, _)| (*name).clone())
.unwrap())
}
LoadBalancingStrategy::LatencyBased => {
Ok(models
.iter()
.min_by(|(_, a), (_, b)| a.latency.cmp(&b.latency))
.map(|(name, _)| (*name).clone())
.unwrap())
}
}
}
pub fn get_route_accuracy(&self) -> f64 {
let cache: _ = self.route_cache.read().unwrap();
if cache.is_empty() {
return 0.0;
}
let valid_entries: _ = cache
.values()
.filter(|(_, time)| time.elapsed() < self.config.route_cache_ttl)
.count();
valid_entries as f64 / cache.len() as f64
}
}
impl ModelManager {
pub fn new(
runtime: &Arc<tokio::runtime::Runtime>,
config: ManagerConfig,
) -> Result<Self, String> {
let registry_config: _ = ModelRegistryConfig {
auto_discovery: true,
health_check_interval: Duration::from_secs(30),
};
let router_config: _ = RouterConfig {
load_balancing: LoadBalancingStrategy::RoundRobin,
fallback_enabled: true,
route_cache_ttl: Duration::from_secs(60),
};
let registry: _ = ModelRegistry::new(registry_config)?;
let router: _ = ModelRouter::new(router_config)?;
Ok(ModelManager {
runtime: runtime.clone(),
config: config.clone(),
registry,
router,
active_models: Arc::new(RwLock::new(HashMap::new())),
model_handles: Arc::new(Mutex::new(Vec::new())),
})
}
pub fn load_model(&mut self, model_name: String) -> Result<ModelHandle, String> {
{
let handles: _ = self.model_handles.lock().unwrap();
if handles.len() >= self.config.max_concurrent_models {
return Err("Max concurrent models reached".to_string());
}
}
{
let active_models: _ = self.active_models.read().unwrap();
if let Some(handle) = active_models.get(&model_name) {
return Ok(handle.clone());
}
}
let load_start: _ = Instant::now();
std::thread::sleep(Duration::from_millis(100)); let load_time: _ = load_start.elapsed();
let handle: _ = ModelHandle {
model_name: model_name.clone(),
handle_id: format!("{}-{}", model_name, load_time.as_nanos()),
load_time: Instant::now(),
last_used: Instant::now(),
};
let metrics: _ = ModelMetrics {
latency: Duration::from_millis(50),
throughput: 100.0,
error_rate: 0.01,
load: 0.0,
};
self.router.add_model(model_name.clone(), metrics);
{
let mut active_models = self.active_models.write().unwrap();
active_models.insert(model_name.clone(), handle.clone());
}
{
let mut handles = self.model_handles.lock().unwrap();
handles.push(handle.clone());
}
Ok(handle)
}
pub fn inference(&self, handle: &ModelHandle, input: String) -> Result<String, String> {
{
let mut active_models = self.active_models.write().unwrap();
if let Some(model_handle) = active_models.get_mut(&handle.model_name) {
model_handle.last_used = Instant::now();
}
}
let inference_start: _ = Instant::now();
std::thread::sleep(Duration::from_millis(10)); let inference_time: _ = inference_start.elapsed();
let metrics: _ = ModelMetrics {
latency: inference_time,
throughput: 100.0,
error_rate: 0.01,
load: 0.5,
};
self.router.add_model(handle.model_name.clone(), metrics);
Ok(format!("Inference result for: {}", input))
}
pub fn get_model_metrics(&self, model_name: &str) -> Result<ModelMetrics, String> {
let model_metrics: _ = self.router.model_metrics.read().unwrap();
if let Some(metrics) = model_metrics.get(model_name) {
Ok(metrics.clone())
} else {
Err("Model not found".to_string())
}
}
pub fn cleanup_idle_models(&self) {
let now: _ = Instant::now();
let timeout: _ = self.config.model_timeout;
let mut to_remove = Vec::new();
{
let active_models: _ = self.active_models.read().unwrap();
for (model_name, handle) in active_models.iter() {
if now.duration_since(handle.last_used) > timeout {
to_remove.push(model_name.clone());
}
}
}
for model_name in to_remove {
self.unload_model(&model_name);
}
}
pub fn unload_model(&self, model_name: &str) {
{
let mut active_models = self.active_models.write().unwrap();
active_models.remove(model_name);
}
{
let mut handles = self.model_handles.lock().unwrap();
handles.retain(|h| h.model_name != model_name);
}
self.registry.mark_unhealthy(model_name);
}
pub fn force_cleanup(&self) {
let mut active_models = self.active_models.write().unwrap();
active_models.clear();
let mut handles = self.model_handles.lock().unwrap();
handles.clear();
}
pub fn register_model(&mut self, model_info: ModelInfo) -> Result<(), String> {
self.registry.register_model(model_info)
}
pub fn route_request(&self, capability: String) -> Result<String, String> {
self.router.route_request(capability)
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_model_registry_creation() {
let config: _ = ModelRegistryConfig {
auto_discovery: true,
health_check_interval: Duration::from_secs(30),
};
let registry: _ = ModelRegistry::new(config);
assert!(registry.is_ok());
}
#[test]
fn test_model_registration() {
let mut registry = ModelRegistry::new(ModelRegistryConfig {
auto_discovery: true,
health_check_interval: Duration::from_secs(30),
})
.unwrap();
let model_info: _ = ModelInfo {
name: "test-model".to_string(),
version: "1.0".to_string(),
model_type: "LanguageModel".to_string(), endpoint: "http://localhost:8080".to_string(),
capabilities: vec!["text-generation".to_string()],
};
let result: _ = registry.register_model(model_info);
assert!(result.is_ok());
}
#[test]
fn test_model_router_creation() {
let config: _ = RouterConfig {
load_balancing: LoadBalancingStrategy::RoundRobin,
fallback_enabled: true,
route_cache_ttl: Duration::from_secs(60),
};
let router: _ = ModelRouter::new(config);
assert!(router.is_ok());
}
#[test]
fn test_intelligent_routing() {
let router: _ = ModelRouter::new(RouterConfig {
load_balancing: LoadBalancingStrategy::LatencyBased,
fallback_enabled: true,
route_cache_ttl: Duration::from_secs(60),
})
.unwrap();
router.add_model(
"model-a".to_string(),
ModelMetrics {
latency: Duration::from_millis(50),
throughput: 100.0,
error_rate: 0.01,
load: 0.3,
},
);
router.add_model(
"model-b".to_string(),
ModelMetrics {
latency: Duration::from_millis(80),
throughput: 150.0,
error_rate: 0.02,
load: 0.5,
},
);
let selected: _ = router.route_request("text-generation".to_string());
assert!(selected.is_ok());
assert_eq!(selected.unwrap(), "model-a".to_string());
}
#[test]
fn test_model_manager_creation() {
let runtime: _ = Arc::new(std::sync::Mutex::new(tokio::runtime::Runtime::new()).unwrap());
let config: _ = ManagerConfig {
max_concurrent_models: 10,
model_timeout: Duration::from_secs(300),
enable_auto_scaling: true,
};
let manager: _ = ModelManager::new(&runtime, config);
assert!(manager.is_ok());
}
#[test]
fn test_model_loading() {
let runtime: _ = Arc::new(std::sync::Mutex::new(tokio::runtime::Runtime::new()).unwrap());
let config: _ = ManagerConfig {
max_concurrent_models: 10,
model_timeout: Duration::from_secs(300),
enable_auto_scaling: true,
};
let mut manager = ModelManager::new(&runtime, config).unwrap();
let handle: _ = manager.load_model("test-model".to_string());
assert!(handle.is_ok());
}
#[test]
fn test_inference() {
let runtime: _ = Arc::new(std::sync::Mutex::new(tokio::runtime::Runtime::new()).unwrap());
let config: _ = ManagerConfig {
max_concurrent_models: 10,
model_timeout: Duration::from_secs(300),
enable_auto_scaling: true,
};
let mut manager = ModelManager::new(&runtime, config).unwrap();
let handle: _ = manager.load_model("test-model".to_string()).unwrap();
let result: _ = manager.inference(&handle, "Test input".to_string());
assert!(result.is_ok());
assert!(result.unwrap().contains("Test input"));
}
#[test]
fn test_model_cleanup() {
let runtime: _ = Arc::new(std::sync::Mutex::new(tokio::runtime::Runtime::new()).unwrap());
let config: _ = ManagerConfig {
max_concurrent_models: 10,
model_timeout: Duration::from_millis(100), enable_auto_scaling: true,
};
let mut manager = ModelManager::new(&runtime, config).unwrap();
manager.load_model("test-model".to_string()).unwrap();
std::thread::sleep(Duration::from_millis(200));
manager.cleanup_idle_models();
let active_models: _ = manager.active_models.read().unwrap();
assert!(!active_models.contains_key("test-model"));
}
}