//! PyO3 bindings for engine types. use crate::core::PyMessage; use openjarvis_engine::InferenceEngine; use pyo3::prelude::*; /// Wraps the Engine enum (static dispatch internally, opaque to Python). #[pyclass(name = "Engine")] pub struct PyEngine { pub inner: openjarvis_engine::Engine, } #[pymethods] impl PyEngine { /// Create an engine by key (e.g. "ollama", "vllm", "sglang", "llamacpp", /// "mlx", "lmstudio", "exo", "nexa", "uzu", "apple_fm"). #[new] #[pyo3(signature = (engine_key="ollama", host=None))] fn new(engine_key: &str, host: Option<&str>) -> PyResult { let engine = match engine_key { "ollama" => openjarvis_engine::Engine::Ollama( openjarvis_engine::OllamaEngine::new( host.unwrap_or("http://localhost:11434"), 120.0, ), ), "vllm" => openjarvis_engine::Engine::Vllm( openjarvis_engine::OpenAICompatEngine::vllm( host.unwrap_or("http://localhost:8000"), ), ), "sglang" => openjarvis_engine::Engine::Sglang( openjarvis_engine::OpenAICompatEngine::sglang( host.unwrap_or("http://localhost:30000"), ), ), "llamacpp" => openjarvis_engine::Engine::LlamaCpp( openjarvis_engine::OpenAICompatEngine::llamacpp( host.unwrap_or("http://localhost:8080"), ), ), "mlx" => openjarvis_engine::Engine::Mlx( openjarvis_engine::OpenAICompatEngine::mlx( host.unwrap_or("http://localhost:8080"), ), ), "lmstudio" => openjarvis_engine::Engine::LmStudio( openjarvis_engine::OpenAICompatEngine::lmstudio( host.unwrap_or("http://localhost:1234"), ), ), "exo" => openjarvis_engine::Engine::Exo( openjarvis_engine::OpenAICompatEngine::exo( host.unwrap_or("http://localhost:52415"), ), ), "nexa" => openjarvis_engine::Engine::Nexa( openjarvis_engine::OpenAICompatEngine::nexa( host.unwrap_or("http://localhost:18181"), ), ), "uzu" => openjarvis_engine::Engine::Uzu( openjarvis_engine::OpenAICompatEngine::uzu( host.unwrap_or("http://localhost:8080"), ), ), "apple_fm" => openjarvis_engine::Engine::AppleFm( openjarvis_engine::OpenAICompatEngine::apple_fm( host.unwrap_or("http://localhost:8079"), ), ), "vllm_native" => openjarvis_engine::Engine::VLLM( openjarvis_engine::VLLMEngine::new( host.unwrap_or("http://localhost"), 8000, None, 120.0, ), ), "sglang_native" => openjarvis_engine::Engine::SGLang( openjarvis_engine::SGLangEngine::new( host.unwrap_or("http://localhost"), 30000, 120.0, ), ), "llamacpp_native" => openjarvis_engine::Engine::LlamaCppNative( openjarvis_engine::LlamaCppEngine::new( host.unwrap_or("http://localhost"), 8080, 120.0, ), ), other => { return Err(PyErr::new::( format!("Unknown engine: {}", other), )); } }; Ok(Self { inner: engine }) } fn engine_id(&self) -> &str { self.inner.engine_id() } fn variant_key(&self) -> &str { self.inner.variant_key() } fn health(&self) -> bool { self.inner.health() } fn list_models(&self) -> PyResult> { self.inner .list_models() .map_err(|e| PyErr::new::(e.to_string())) } #[pyo3(signature = (messages, model, temperature=0.7, max_tokens=1024))] fn generate( &self, messages: Vec, model: &str, temperature: f64, max_tokens: i64, ) -> PyResult { let core_msgs: Vec = messages.iter().map(|m| m.to_core()).collect(); let result = self .inner .generate(&core_msgs, model, temperature, max_tokens, None) .map_err(|e| PyErr::new::(e.to_string()))?; Ok(serde_json::to_string(&result).unwrap_or_default()) } fn __repr__(&self) -> String { format!("Engine({})", self.inner.variant_key()) } } /// Convenience alias for backward compatibility. #[pyclass(name = "OllamaEngine")] pub struct PyOllamaEngine { inner: openjarvis_engine::OllamaEngine, } #[pymethods] impl PyOllamaEngine { #[new] #[pyo3(signature = (host="http://localhost:11434", timeout=120.0))] fn new(host: &str, timeout: f64) -> Self { Self { inner: openjarvis_engine::OllamaEngine::new(host, timeout), } } fn engine_id(&self) -> &str { self.inner.engine_id() } fn health(&self) -> bool { self.inner.health() } fn list_models(&self) -> PyResult> { self.inner .list_models() .map_err(|e| PyErr::new::(e.to_string())) } #[pyo3(signature = (messages, model, temperature=0.7, max_tokens=1024))] fn generate( &self, messages: Vec, model: &str, temperature: f64, max_tokens: i64, ) -> PyResult { let core_msgs: Vec = messages.iter().map(|m| m.to_core()).collect(); let result = self .inner .generate(&core_msgs, model, temperature, max_tokens, None) .map_err(|e| PyErr::new::(e.to_string()))?; Ok(serde_json::to_string(&result).unwrap_or_default()) } }