use std::collections::HashMap; use std::path::Path; use anyhow::Context; use platform_config::ConfigLoader; use serde::{Deserialize, Serialize}; /// AI Service configuration #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct AiServiceConfig { pub ai_service: AiServiceSettings, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct AiServiceSettings { pub server: ServerConfig, pub rag: RagConfig, pub mcp: McpConfig, pub dag: DagConfig, #[serde(default)] pub monitoring: Option, #[serde(default)] pub logging: Option, #[serde(default)] pub build: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ServerConfig { pub host: String, pub port: u16, #[serde(default)] pub workers: Option, } impl Default for ServerConfig { fn default() -> Self { Self { host: "127.0.0.1".to_string(), port: 8082, workers: Some(4), } } } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct RagConfig { pub enabled: bool, pub rag_service_url: Option, pub timeout: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct McpConfig { pub enabled: bool, pub mcp_service_url: Option, pub timeout: Option, } #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct DagConfig { pub max_concurrent_tasks: Option, pub task_timeout: Option, pub retry_attempts: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct MonitoringConfig { pub enabled: bool, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LoggingConfig { pub level: String, } #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DockerBuildConfig { pub base_image: String, #[serde(default)] pub build_args: HashMap, } impl AiServiceConfig { pub fn load() -> anyhow::Result { let config_json = platform_config::load_service_config_from_ncl("ai-service") .context("Failed to load ai-service configuration from Nickel")?; serde_json::from_value(config_json) .context("Failed to deserialize ai-service configuration") } } impl ConfigLoader for AiServiceConfig { fn service_name() -> &'static str { "ai-service" } fn load_from_hierarchy() -> std::result::Result> { if let Some(path) = platform_config::resolve_config_path(Self::service_name()) { return Self::from_path(&path); } Ok(Self::default()) } fn apply_env_overrides( &mut self, ) -> std::result::Result<(), Box> { if let Ok(host) = std::env::var("AI_SERVICE_SERVER_HOST") { self.ai_service.server.host = host; } if let Ok(port) = std::env::var("AI_SERVICE_SERVER_PORT") { if let Ok(p) = port.parse() { self.ai_service.server.port = p; } } Ok(()) } fn from_path>( path: P, ) -> std::result::Result> { let path = path.as_ref(); let json_value = platform_config::format::load_config(path) .map_err(|e| Box::new(e) as Box)?; serde_json::from_value(json_value).map_err(|e| { let err_msg = format!("Failed to deserialize ai-service config: {}", e); Box::new(std::io::Error::new( std::io::ErrorKind::InvalidData, err_msg, )) as Box }) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_default_config() { let config = AiServiceConfig::default(); assert_eq!(config.ai_service.server.port, 8082); assert!(!config.ai_service.rag.enabled); assert!(!config.ai_service.mcp.enabled); } }