#![allow(missing_docs)]
use paladin_core::platform::container::content::ContentItem;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MlPredictionRequest {
pub model_name: String,
pub input_data: MlInputData,
pub parameters: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MlInputData {
Text(String),
Image(Vec<u8>),
Audio(Vec<u8>),
Video(Vec<u8>),
Structured(HashMap<String, serde_json::Value>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MlPredictionResponse {
pub model_name: String,
pub predictions: Vec<MlPrediction>,
pub confidence_scores: Option<Vec<f64>>,
pub processing_time_ms: u64,
pub metadata: Option<HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MlPrediction {
Classification { class: String, confidence: f64 },
Regression { value: f64 },
TextGeneration { text: String },
ObjectDetection { objects: Vec<DetectedObject> },
Sentiment { sentiment: String, confidence: f64 },
Embedding { vector: Vec<f64> },
Custom(HashMap<String, serde_json::Value>),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct DetectedObject {
pub class: String,
pub confidence: f64,
pub bounding_box: Option<BoundingBox>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BoundingBox {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
#[derive(Debug, Clone, Error)]
pub enum MlPortError {
#[error("Model not found: {0}")]
ModelNotFound(String),
#[error("Invalid input format: {0}")]
InvalidInput(String),
#[error("Prediction failed: {0}")]
PredictionFailed(String),
#[error("Model loading error: {0}")]
ModelLoadingError(String),
#[error("Unsupported content type for ML processing")]
UnsupportedContentType,
#[error("ML service unavailable")]
ServiceUnavailable,
}
pub trait MlPort {
fn predict(&self, request: MlPredictionRequest) -> Result<MlPredictionResponse, MlPortError>;
fn analyze_content(
&self,
content: ContentItem,
model_name: &str,
) -> Result<ContentItem, MlPortError>;
fn list_models(&self) -> Result<Vec<String>, MlPortError>;
fn is_model_available(&self, model_name: &str) -> Result<bool, MlPortError>;
fn get_model_info(&self, model_name: &str) -> Result<MlModelInfo, MlPortError>;
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct MlModelInfo {
pub name: String,
pub version: String,
pub description: Option<String>,
pub input_types: Vec<String>,
pub output_types: Vec<String>,
pub parameters: Option<HashMap<String, serde_json::Value>>,
}
impl MlInputData {
pub fn from_content_item(content_item: &ContentItem) -> Result<Self, MlPortError> {
use paladin_core::platform::container::content::ContentType;
match content_item.content() {
ContentType::Text(text_content) => {
if let Some(ref content) = text_content.content {
Ok(MlInputData::Text(content.clone()))
} else if let Some(ref path) = text_content.path {
let content = std::fs::read_to_string(path).map_err(|e| {
MlPortError::InvalidInput(format!("Failed to read text file: {}", e))
})?;
Ok(MlInputData::Text(content))
} else {
Err(MlPortError::InvalidInput(
"No text content available".to_string(),
))
}
}
ContentType::Image(image_content) => {
if let Some(ref path) = image_content.path {
let bytes = std::fs::read(path).map_err(|e| {
MlPortError::InvalidInput(format!("Failed to read image file: {}", e))
})?;
Ok(MlInputData::Image(bytes))
} else {
Err(MlPortError::InvalidInput(
"No image file path available".to_string(),
))
}
}
ContentType::Audio(audio_content) => {
if let Some(ref path) = audio_content.path {
let bytes = std::fs::read(path).map_err(|e| {
MlPortError::InvalidInput(format!("Failed to read audio file: {}", e))
})?;
Ok(MlInputData::Audio(bytes))
} else {
Err(MlPortError::InvalidInput(
"No audio file path available".to_string(),
))
}
}
ContentType::Video(video_content) => {
if let Some(ref path) = video_content.path {
let bytes = std::fs::read(path).map_err(|e| {
MlPortError::InvalidInput(format!("Failed to read video file: {}", e))
})?;
Ok(MlInputData::Video(bytes))
} else {
Err(MlPortError::InvalidInput(
"No video file path available".to_string(),
))
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use paladin_core::platform::container::content::{ContentType, TextContent};
#[test]
fn test_ml_input_data_from_text_content() {
let text_content = TextContent::new(None, Some("Hello world".to_string())).unwrap();
let content_item = ContentItem::new(ContentType::Text(text_content)).unwrap();
let ml_input = MlInputData::from_content_item(&content_item).unwrap();
match ml_input {
MlInputData::Text(text) => assert_eq!(text, "Hello world"),
_ => panic!("Expected text input data"),
}
}
#[test]
fn test_ml_prediction_serialization() {
let prediction = MlPrediction::Classification {
class: "positive".to_string(),
confidence: 0.95,
};
let json = serde_json::to_string(&prediction).unwrap();
let deserialized: MlPrediction = serde_json::from_str(&json).unwrap();
assert_eq!(prediction, deserialized);
}
}