oxirs_embed/api/handlers/
models.rs1#[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#[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 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 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 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#[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 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 let models = state.models.read().await;
116 let is_loaded = models.contains_key(&model_id);
117
118 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 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, requests_last_hour: cache_stats.total_hits + cache_stats.total_misses,
141 error_rate_percent: 0.0, 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 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 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#[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 if state.registry.get_model(model_id).await.is_err() {
202 return Err(StatusCode::NOT_FOUND);
203 }
204
205 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#[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 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 {
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 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#[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 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 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}