Skip to main content

oxirs_embed/api/handlers/
models.rs

1//! Model management HTTP handlers
2//!
3//! This module contains handlers for model lifecycle management endpoints.
4
5#[cfg(feature = "api-server")]
6use super::super::{ApiState, HealthMetrics, HealthStatus, ModelHealth, ModelInfoResponse};
7#[cfg(feature = "api-server")]
8use crate::{ModelStats, TrainingStats};
9#[cfg(feature = "api-server")]
10use axum::{
11    extract::{Path, Query, State},
12    http::StatusCode,
13    response::Json,
14};
15#[cfg(feature = "api-server")]
16use chrono::Utc;
17#[cfg(feature = "api-server")]
18use serde_json::json;
19#[cfg(feature = "api-server")]
20use std::collections::HashMap;
21#[cfg(feature = "api-server")]
22use tracing::{debug, error, info, warn};
23use uuid::Uuid;
24
25/// List available models
26#[cfg(feature = "api-server")]
27pub async fn list_models(
28    State(state): State<ApiState>,
29    Query(params): Query<HashMap<String, String>>,
30) -> Result<Json<serde_json::Value>, StatusCode> {
31    debug!("Listing available models");
32
33    // Get all models from registry
34    let models = state.registry.list_models().await;
35    let loaded_models = state.models.read().await;
36
37    let mut model_list = Vec::new();
38    for model_metadata in models {
39        let is_loaded = loaded_models.contains_key(&model_metadata.model_id);
40
41        // Get production version info if available
42        let production_version = if let Some(prod_version_id) = model_metadata.production_version {
43            match state.registry.get_version(prod_version_id).await {
44                Ok(version) => Some(json!({
45                    "version_id": version.version_id,
46                    "version_number": version.version_number,
47                    "created_at": version.created_at,
48                    "is_production": version.is_production
49                })),
50                Err(_) => None,
51            }
52        } else {
53            None
54        };
55
56        let model_info = json!({
57            "model_id": model_metadata.model_id,
58            "name": model_metadata.name,
59            "model_type": model_metadata.model_type,
60            "created_at": model_metadata.created_at,
61            "updated_at": model_metadata.updated_at,
62            "owner": model_metadata.owner,
63            "description": model_metadata.description,
64            "is_loaded": is_loaded,
65            "version_count": model_metadata.versions.len(),
66            "production_version": production_version,
67            "staging_version": model_metadata.staging_version
68        });
69
70        model_list.push(model_info);
71    }
72
73    // Apply filters if requested
74    let detailed = params.get("detailed").map(|v| v == "true").unwrap_or(false);
75
76    let response = if detailed {
77        json!({
78            "models": model_list,
79            "total_count": model_list.len(),
80            "loaded_count": loaded_models.len()
81        })
82    } else {
83        json!({
84            "models": model_list.iter().map(|m| json!({
85                "model_id": m["model_id"],
86                "name": m["name"],
87                "model_type": m["model_type"],
88                "is_loaded": m["is_loaded"]
89            })).collect::<Vec<_>>(),
90            "total_count": model_list.len()
91        })
92    };
93
94    Ok(Json(response))
95}
96
97/// Get model information
98#[cfg(feature = "api-server")]
99pub async fn get_model_info(
100    State(state): State<ApiState>,
101    Path(model_id): Path<Uuid>,
102) -> Result<Json<ModelInfoResponse>, StatusCode> {
103    debug!("Getting model information for: {}", model_id);
104
105    // Get model metadata from registry
106    let model_metadata = match state.registry.get_model(model_id).await {
107        Ok(metadata) => metadata,
108        Err(e) => {
109            error!("Model not found in registry: {}", e);
110            return Err(StatusCode::NOT_FOUND);
111        }
112    };
113
114    // Check if model is loaded
115    let models = state.models.read().await;
116    let is_loaded = models.contains_key(&model_id);
117
118    // Get model stats
119    let stats = if let Some(model) = models.get(&model_id) {
120        model.get_stats()
121    } else {
122        ModelStats {
123            num_entities: 0,
124            num_relations: 0,
125            num_triples: 0,
126            dimensions: 0,
127            is_trained: false,
128            model_type: model_metadata.model_type.clone(),
129            creation_time: model_metadata.created_at,
130            last_training_time: None,
131        }
132    };
133
134    // Get health status
135    let cache_stats = state.cache_manager.get_stats();
136    let memory_usage_mb = state.cache_manager.estimate_memory_usage() as f64 / (1024.0 * 1024.0);
137
138    let health_metrics = HealthMetrics {
139        avg_response_time_ms: 50.0, // Would need proper tracking
140        requests_last_hour: cache_stats.total_hits + cache_stats.total_misses,
141        error_rate_percent: 0.0, // Would need proper error tracking
142        memory_usage_mb,
143    };
144
145    let health_status = if is_loaded && stats.is_trained {
146        HealthStatus::Healthy
147    } else if is_loaded {
148        HealthStatus::Degraded
149    } else {
150        HealthStatus::Unhealthy
151    };
152
153    let health = ModelHealth {
154        status: health_status,
155        last_check: Utc::now(),
156        metrics: health_metrics,
157    };
158
159    // Get capabilities
160    let capabilities = vec![
161        "entity_embedding".to_string(),
162        "relation_embedding".to_string(),
163        "triple_scoring".to_string(),
164        "object_prediction".to_string(),
165        "subject_prediction".to_string(),
166        "relation_prediction".to_string(),
167    ];
168
169    // Get last training stats (placeholder - would need proper tracking)
170    let last_training = if stats.is_trained {
171        Some(TrainingStats {
172            epochs_completed: 100,
173            final_loss: 0.1,
174            training_time_seconds: 3600.0,
175            convergence_achieved: true,
176            loss_history: vec![1.0, 0.5, 0.2, 0.1],
177        })
178    } else {
179        None
180    };
181
182    let response = ModelInfoResponse {
183        stats,
184        health,
185        capabilities,
186        last_training,
187    };
188
189    Ok(Json(response))
190}
191
192/// Get model health status
193#[cfg(feature = "api-server")]
194pub async fn get_model_health(
195    State(state): State<ApiState>,
196    Path(model_id): Path<Uuid>,
197) -> Result<Json<serde_json::Value>, StatusCode> {
198    debug!("Getting health status for model: {}", model_id);
199
200    // Check if model exists in registry
201    if state.registry.get_model(model_id).await.is_err() {
202        return Err(StatusCode::NOT_FOUND);
203    }
204
205    // Check if model is loaded
206    let models = state.models.read().await;
207    let is_loaded = models.contains_key(&model_id);
208
209    let health_status = if let Some(model) = models.get(&model_id) {
210        if model.is_trained() {
211            "healthy"
212        } else {
213            "degraded"
214        }
215    } else {
216        "unhealthy"
217    };
218
219    let response = json!({
220        "model_id": model_id,
221        "status": health_status,
222        "is_loaded": is_loaded,
223        "last_check": Utc::now(),
224        "details": {
225            "loaded": is_loaded,
226            "trained": models.get(&model_id).map(|m| m.is_trained()).unwrap_or(false),
227            "memory_usage_mb": state.cache_manager.estimate_memory_usage() as f64 / (1024.0 * 1024.0)
228        }
229    });
230
231    Ok(Json(response))
232}
233
234/// Load a model
235#[cfg(feature = "api-server")]
236pub async fn load_model(
237    State(state): State<ApiState>,
238    Path(model_id): Path<Uuid>,
239) -> Result<Json<serde_json::Value>, StatusCode> {
240    info!("Loading model: {}", model_id);
241
242    // Check if model exists in registry
243    let model_metadata = match state.registry.get_model(model_id).await {
244        Ok(metadata) => metadata,
245        Err(e) => {
246            error!("Model not found in registry: {}", e);
247            return Err(StatusCode::NOT_FOUND);
248        }
249    };
250
251    // Check if model is already loaded
252    {
253        let models = state.models.read().await;
254        if models.contains_key(&model_id) {
255            let response = json!({
256                "status": "already_loaded",
257                "model_id": model_id,
258                "message": "Model is already loaded"
259            });
260            return Ok(Json(response));
261        }
262    }
263
264    // In a real implementation, this would:
265    // 1. Load model weights from storage
266    // 2. Initialize model with proper configuration
267    // 3. Add to the loaded models map
268    // For now, we'll create a placeholder response
269
270    warn!("Model loading not fully implemented - this would load model weights and configuration");
271
272    let response = json!({
273        "status": "load_initiated",
274        "model_id": model_id,
275        "model_name": model_metadata.name,
276        "model_type": model_metadata.model_type,
277        "message": "Model loading initiated (implementation pending)"
278    });
279
280    Ok(Json(response))
281}
282
283/// Unload a model
284#[cfg(feature = "api-server")]
285pub async fn unload_model(
286    State(state): State<ApiState>,
287    Path(model_id): Path<Uuid>,
288) -> Result<Json<serde_json::Value>, StatusCode> {
289    info!("Unloading model: {}", model_id);
290
291    // Check if model exists and is loaded
292    let was_loaded = {
293        let mut models = state.models.write().await;
294        models.remove(&model_id).is_some()
295    };
296
297    if !was_loaded {
298        let response = json!({
299            "status": "not_loaded",
300            "model_id": model_id,
301            "message": "Model was not loaded"
302        });
303        return Ok(Json(response));
304    }
305
306    // Clear any cached data for this model
307    state
308        .cache_manager
309        .clear_computation_cache(&model_id.to_string());
310
311    let response = json!({
312        "status": "unloaded",
313        "model_id": model_id,
314        "message": "Model unloaded successfully"
315    });
316
317    Ok(Json(response))
318}