diff --git a/litellm-rust/Cargo.lock b/litellm-rust/Cargo.lock index 7addc4e3e45..8149ca7ee3f 100644 --- a/litellm-rust/Cargo.lock +++ b/litellm-rust/Cargo.lock @@ -1475,6 +1475,7 @@ dependencies = [ "pyo3-async-runtimes", "serde", "serde_json", + "thiserror 2.0.19", "tokio", "tokio-tungstenite", "tracing", diff --git a/litellm-rust/crates/python-bridge/Cargo.toml b/litellm-rust/crates/python-bridge/Cargo.toml index bda09a7d840..3d18fadbfd7 100644 --- a/litellm-rust/crates/python-bridge/Cargo.toml +++ b/litellm-rust/crates/python-bridge/Cargo.toml @@ -30,6 +30,7 @@ pyo3.workspace = true pyo3-async-runtimes.workspace = true serde.workspace = true serde_json.workspace = true +thiserror.workspace = true tokio.workspace = true [dev-dependencies] diff --git a/litellm-rust/crates/python-bridge/src/errors.rs b/litellm-rust/crates/python-bridge/src/errors.rs index 407475dedce..9dc62e33120 100644 --- a/litellm-rust/crates/python-bridge/src/errors.rs +++ b/litellm-rust/crates/python-bridge/src/errors.rs @@ -16,6 +16,10 @@ pyo3::create_exception!( "The provider call was already issued and failed. Args are (status, message); status is 0 when there was no HTTP response." ); +fn upstream_error(status: u16, message: String) -> PyErr { + RustUpstreamError::new_err((status, message)) +} + pub(crate) fn core_error_to_pyerr(err: Error) -> PyErr { match err { Error::Auth(message) => PyValueError::new_err(message), @@ -46,11 +50,9 @@ pub(crate) fn chat_completions_error_to_pyerr(err: Error) -> PyErr { // Nothing reached the provider, so serving it on Python cannot double // bill and is the only way the caller gets an answer at all. | Error::Connect(_) => RustBridgeDeclined::new_err(err.to_string()), - Error::Http { status, body } => { - RustUpstreamError::new_err((status, format!("{status}: {body}"))) - } + Error::Http { status, body } => upstream_error(status, format!("{status}: {body}")), Error::Network(message) | Error::InvalidResponse(message) => { - RustUpstreamError::new_err((0u16, message)) + upstream_error(0, message) } } } @@ -66,7 +68,7 @@ pub(crate) fn ocr_error_to_pyerr(err: Error) -> PyErr { Error::MissingField("document_url" | "image_url") => { PyValueError::new_err("Document URL is required") } - Error::Http { status, body } => RustUpstreamError::new_err((status, body)), + Error::Http { status, body } => upstream_error(status, body), other => core_error_to_pyerr(other), } } diff --git a/litellm-rust/crates/python-bridge/src/execution.rs b/litellm-rust/crates/python-bridge/src/execution.rs index f3648158cf6..446a48c416c 100644 --- a/litellm-rust/crates/python-bridge/src/execution.rs +++ b/litellm-rust/crates/python-bridge/src/execution.rs @@ -11,6 +11,18 @@ use serde::Serialize; use tokio::runtime::{Handle, Runtime}; use tokio::time::{self, MissedTickBehavior}; +#[derive(Debug, thiserror::Error)] +enum ExecutionError { + #[error("synchronous native routes cannot run from a Tokio context; use the async route")] + SyncRouteInTokioContext, +} + +impl From for PyErr { + fn from(error: ExecutionError) -> Self { + PyRuntimeError::new_err(error.to_string()) + } +} + pub(crate) fn run_sync( py: Python<'_>, future: F, @@ -39,9 +51,7 @@ where F: Future> + Send + 'static, { if Handle::try_current().is_ok() { - return Err(PyRuntimeError::new_err( - "synchronous native routes cannot run from a Tokio context; use the async route", - )); + return Err(ExecutionError::SyncRouteInTokioContext.into()); } let result = release_gil(py, move || runtime.block_on(wait_for_sync_result(future)))?;