From ab0875b2dabb8ad9c21baa9f1643b2f873cecf4b Mon Sep 17 00:00:00 2001 From: yujonglee Date: Wed, 14 Oct 2026 17:19:06 -0700 Subject: [PATCH] refactor(host-python): collect host failures in a thiserror Error enum (#46361) * refactor(host-python): collect host failures in a thiserror Error enum missing_state() and the inline PyRuntimeError/PyTypeError messages become Error variants with one From for PyErr, and InvokeError derives thiserror so From comes from #[from] Co-Authored-By: Claude Opus 5.5 * docs(host-python): note the canonical HostError import alias in AGENTS.md Co-Authored-By: Yujong Lee --------- Co-authored-by: Claude Opus 5.5 Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm-rust/Cargo.lock | 1 + .../callbacks-legacy-python/src/adapter.rs | 12 ++-- litellm-rust/crates/host-python/AGENTS.md | 1 + litellm-rust/crates/host-python/Cargo.toml | 1 + litellm-rust/crates/host-python/src/driver.rs | 16 +++-- litellm-rust/crates/host-python/src/error.rs | 63 +++++++++++++++---- .../crates/host-python/src/file_reader.rs | 11 ++-- litellm-rust/crates/host-python/src/gil.rs | 6 +- litellm-rust/crates/host-python/src/handle.rs | 16 ++--- .../host-python/src/hooks/chain/adapter.rs | 10 +-- .../host-python/src/hooks/chain/dispatch.rs | 12 ++-- litellm-rust/crates/host-python/src/lib.rs | 2 +- litellm-rust/crates/host-python/src/native.rs | 13 ++-- .../crates/host-python/src/runtime.rs | 10 +-- .../crates/host-python/tests/driver.rs | 8 +-- .../crates/python-bridge/src/routes/mod.rs | 8 ++- .../python-bridge/src/routes/ocr/host.rs | 8 +-- 17 files changed, 115 insertions(+), 83 deletions(-) diff --git a/litellm-rust/Cargo.lock b/litellm-rust/Cargo.lock index 2f77b09221a..9edbc31125e 100644 --- a/litellm-rust/Cargo.lock +++ b/litellm-rust/Cargo.lock @@ -3929,6 +3929,7 @@ dependencies = [ "rstest", "serde", "serde_json", + "thiserror 2.0.19", "tokio", ] diff --git a/litellm-rust/crates/callbacks-legacy-python/src/adapter.rs b/litellm-rust/crates/callbacks-legacy-python/src/adapter.rs index 9f56f12a8f9..d891b5a1f0c 100644 --- a/litellm-rust/crates/callbacks-legacy-python/src/adapter.rs +++ b/litellm-rust/crates/callbacks-legacy-python/src/adapter.rs @@ -9,7 +9,7 @@ use litellm_host::{ interceptors::{RawResponse, RequestContext, WireRequest}, lifecycle::{FailureOrigin, Timing, epoch_seconds}, }; -use litellm_host_python::{HookStep, from_py, missing_state, to_py}; +use litellm_host_python::{Error as HostError, HookStep, from_py, to_py}; use pyo3::{ exceptions::{PyBaseException, PyException}, gc::{PyTraverseError, PyVisit}, @@ -160,7 +160,7 @@ impl LegacyLogging { self.response .as_ref() .map(|response| HookStep::Ready(response.clone_ref(py))) - .ok_or_else(missing_state) + .ok_or_else(|| HostError::MissingState.into()) } fn dispatch_success(&self, py: Python<'_>) -> PyResult<()> { @@ -209,7 +209,7 @@ impl LegacyLogging { fn stream_success(&self, py: Python<'_>, stream: &DeliveredStream) -> PyResult<()> { let logger = self.logger()?; - let billing = self.stream_billing().ok_or_else(missing_state)?; + let billing = self.stream_billing().ok_or(HostError::MissingState)?; let billed = Streaming::Success.call( py, ( @@ -525,7 +525,7 @@ impl LegacyLogging { return self.stream_failure(py); } if origin == FailureOrigin::Call && self.logger.is_some() && self.runs_deployment_hooks() { - let error = self.error.as_ref().ok_or_else(missing_state)?; + let error = self.error.as_ref().ok_or(HostError::MissingState)?; return Ok(HookStep::Await( DeploymentHooks::after_failure(py, self.call.kwargs(), error, self.call_type())?, Self::resume_deployment_failure, @@ -536,7 +536,7 @@ impl LegacyLogging { pub(crate) fn stream_opened(&mut self, py: Python<'_>, head: &Py) -> PyResult<()> { if self.stream_billing().is_none() { - return Err(missing_state()); + return Err(HostError::MissingState.into()); } if let Some(key) = &self.cache_key { head.bind(py).set_item("cache_key", key)?; @@ -551,7 +551,7 @@ impl LegacyLogging { } pub(crate) fn stream_chunk(&mut self, py: Python<'_>, chunk: &Py) -> PyResult<()> { - let stream = self.stream.as_mut().ok_or_else(missing_state)?; + let stream = self.stream.as_mut().ok_or(HostError::MissingState)?; if stream.first_chunk.is_none() { stream.first_chunk = Some(datetime(py, epoch_seconds())?); } diff --git a/litellm-rust/crates/host-python/AGENTS.md b/litellm-rust/crates/host-python/AGENTS.md index 7776df32de4..e511cc74a78 100644 --- a/litellm-rust/crates/host-python/AGENTS.md +++ b/litellm-rust/crates/host-python/AGENTS.md @@ -28,6 +28,7 @@ Terminal notifications share the selected response or exception. An ordinary not - `PythonBinding::decode_request` receives the keyword view returned by `prepare_arguments` and updated by `arguments_prepared`, not the caller's dict. A binding that decodes from it inherits all composed argument rewrites - A native failure, including one a host op returns as `InvokeError::Native`, is classified exactly once through the binding's `map_error`. A Python exception raised inside the call, and a failure in `prepare_arguments` or `transform_response`, is raised as is - A failing `map_error` is raised with the native error's text as its `__context__`, never swallowed +- Consumers import this crate's error as `use litellm_host_python::Error as HostError`, so it never shadows their own `Error` - Use standard PyO3 ownership and conversion APIs - Prefer `Bound<'py, T>` for attached operations/results, `Py` for retention. Binding/unbinding does not copy payloads - Use `pythonize` for selected Serde data, never a JSON-text round trip. Share conversion with `Pythonized` diff --git a/litellm-rust/crates/host-python/Cargo.toml b/litellm-rust/crates/host-python/Cargo.toml index bb77a1bf330..bd103b566d5 100644 --- a/litellm-rust/crates/host-python/Cargo.toml +++ b/litellm-rust/crates/host-python/Cargo.toml @@ -11,6 +11,7 @@ litellm-host.workspace = true bytes.workspace = true futures-util.workspace = true serde.workspace = true +thiserror.workspace = true tokio = { workspace = true, features = ["rt", "sync"] } pyo3.workspace = true diff --git a/litellm-rust/crates/host-python/src/driver.rs b/litellm-rust/crates/host-python/src/driver.rs index a1450ed6308..7c89b6cc032 100644 --- a/litellm-rust/crates/host-python/src/driver.rs +++ b/litellm-rust/crates/host-python/src/driver.rs @@ -18,7 +18,7 @@ use crate::PythonHostCalls; use crate::handle::{Execution, ExecutionBody, ExecutionStep, PythonLifecycle}; use crate::hooks::{HookResume, HookStep, PythonCallEvent, PythonCallHooks}; use crate::native::{NativeMachine, NativePoll}; -use crate::{InvokeError, PythonBinding, missing_state}; +use crate::{Error, InvokeError, PythonBinding}; type ProtocolOf = ::Protocol; type ErrorOf = as Protocol>::Error; @@ -158,9 +158,7 @@ where ExecutionStep::Open(head) => Execution::suspended(driver, options.lifecycle) .into_sync_stream(py, head) .map(Bound::unbind), - ExecutionStep::Await(_) | ExecutionStep::Yield(_) => { - Err(PyRuntimeError::new_err("sync call suspended")) - } + ExecutionStep::Await(_) | ExecutionStep::Yield(_) => Err(Error::SyncCallSuspended.into()), } } @@ -254,7 +252,7 @@ where Err(error) => self.hook_failed(py, error), } } - _ => Err(missing_state()), + _ => Err(Error::MissingState.into()), } } @@ -281,8 +279,8 @@ where return self.failure(py, error, FailureOrigin::Call); } }; - let start = self.start.take().ok_or_else(missing_state)?; - let arguments = self.arguments.as_ref().ok_or_else(missing_state)?; + let start = self.start.take().ok_or(Error::MissingState)?; + let arguments = self.arguments.as_ref().ok_or(Error::MissingState)?; let machine = match start(py, arguments.bind(py), request) { Ok(machine) => machine, Err(error) => return self.failure(py, error, FailureOrigin::Host), @@ -357,7 +355,7 @@ where Stage::Failed(error) => { Err(PyErr::from_value(error.bind(py).clone().into_any())) } - _ => Err(missing_state()), + _ => Err(Error::MissingState.into()), } } }, @@ -365,7 +363,7 @@ where } fn begin(&mut self, py: Python<'_>) -> PyResult { - let arguments = self.arguments.take().ok_or_else(missing_state)?; + let arguments = self.arguments.take().ok_or(Error::MissingState)?; match self.hooks.prepare_arguments(py, arguments, self.started_at) { Ok(step) => self.on_arguments(py, step), Err(error) => self.hook_failed(py, error), diff --git a/litellm-rust/crates/host-python/src/error.rs b/litellm-rust/crates/host-python/src/error.rs index d6e186860d7..bec2ff20924 100644 --- a/litellm-rust/crates/host-python/src/error.rs +++ b/litellm-rust/crates/host-python/src/error.rs @@ -1,20 +1,57 @@ -use pyo3::prelude::*; +use pyo3::{ + PyErr, + exceptions::{PyRuntimeError, PyTypeError}, +}; + +/// A failure host-python raises itself, surfacing in Python as the exception `PyErr::from` picks. +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("missing native call state")] + MissingState, + #[error("execution is already running")] + ExecutionRunning, + #[error("execution is closed")] + ExecutionClosed, + #[error("execution requires start before resume and can only start once")] + ExecutionOutOfOrder, + #[error("execution was closed while running")] + ExecutionClosedWhileRunning, + #[error("native execution closed")] + NativeExecutionClosed, + #[error("sync call suspended")] + SyncCallSuspended, + #[error("synchronous native routes cannot run from a Tokio context; use the async route")] + SyncInsideTokio, + #[error("the context body produced no value")] + ContextBodyProducedNothing, + #[error("file read must return bytes or str, got {got}")] + FileContentType { got: String }, +} + +impl From for PyErr { + fn from(error: Error) -> Self { + match error { + Error::FileContentType { .. } => PyTypeError::new_err(error.to_string()), + Error::MissingState + | Error::ExecutionRunning + | Error::ExecutionClosed + | Error::ExecutionOutOfOrder + | Error::ExecutionClosedWhileRunning + | Error::NativeExecutionClosed + | Error::SyncCallSuspended + | Error::SyncInsideTokio + | Error::ContextBodyProducedNothing => PyRuntimeError::new_err(error.to_string()), + } + } +} /// Why a custom operation the host answered did not produce a result: the route's own code /// rejected it, which the route classifies like any other native failure, or Python code /// raised, which reaches the caller as it was raised. -#[derive(Debug)] +#[derive(Debug, thiserror::Error)] pub enum InvokeError { + #[error("{0}")] Native(E), - Python(PyErr), -} - -impl From for InvokeError { - fn from(error: PyErr) -> Self { - Self::Python(error) - } -} - -pub fn missing_state() -> PyErr { - pyo3::exceptions::PyRuntimeError::new_err("missing native call state") + #[error(transparent)] + Python(#[from] PyErr), } diff --git a/litellm-rust/crates/host-python/src/file_reader.rs b/litellm-rust/crates/host-python/src/file_reader.rs index bbc7a233b28..f9596075bb6 100644 --- a/litellm-rust/crates/host-python/src/file_reader.rs +++ b/litellm-rust/crates/host-python/src/file_reader.rs @@ -3,13 +3,14 @@ use bytes::Bytes; use pyo3::{ - exceptions::PyTypeError, gc::{PyTraverseError, PyVisit}, prelude::*, pybacked::PyBackedBytes, types::{PyBytes, PyString}, }; +use crate::Error; + #[derive(Clone, Debug, PartialEq, Eq)] pub struct FileContent { pub bytes: Bytes, @@ -50,10 +51,10 @@ impl PythonFileReader { } else if value.is_instance_of::() { py_bytes(&value)? } else { - return Err(PyTypeError::new_err(format!( - "file read must return bytes or str, got {}", - value.get_type(), - ))); + return Err(Error::FileContentType { + got: value.get_type().to_string(), + } + .into()); }; Ok(FileContent { bytes, diff --git a/litellm-rust/crates/host-python/src/gil.rs b/litellm-rust/crates/host-python/src/gil.rs index dc91c8987b7..aacecb3af81 100644 --- a/litellm-rust/crates/host-python/src/gil.rs +++ b/litellm-rust/crates/host-python/src/gil.rs @@ -3,7 +3,9 @@ use std::sync::{ atomic::{AtomicU64, Ordering}, }; -use pyo3::{exceptions::PyRuntimeError, prelude::*}; +use pyo3::prelude::*; + +use crate::Error; /// The caller's `contextvars` context, captured at the Python entry point so blocking Python /// work started from Rust sees the same request-local values as the Python caller. @@ -49,7 +51,7 @@ impl PythonContext { .lock() .expect("context value slot poisoned") .take() - .ok_or_else(|| PyRuntimeError::new_err("the context body produced no value")) + .ok_or_else(|| Error::ContextBodyProducedNothing.into()) } } diff --git a/litellm-rust/crates/host-python/src/handle.rs b/litellm-rust/crates/host-python/src/handle.rs index cca9059341a..134c088583e 100644 --- a/litellm-rust/crates/host-python/src/handle.rs +++ b/litellm-rust/crates/host-python/src/handle.rs @@ -1,7 +1,7 @@ use std::panic::{AssertUnwindSafe, catch_unwind}; -use crate::panic_to_pyerr; -use pyo3::exceptions::{PyBaseException, PyRuntimeError}; +use crate::{Error, panic_to_pyerr}; +use pyo3::exceptions::PyBaseException; use pyo3::gc::{PyTraverseError, PyVisit}; use pyo3::prelude::*; @@ -76,15 +76,13 @@ impl Execution { match (&execution.state, result.is_some()) { (ExecutionState::Created(_), false) | (ExecutionState::Suspended(_), true) => {} (ExecutionState::Running, _) => { - return Err(PyRuntimeError::new_err("execution is already running")); + return Err(Error::ExecutionRunning.into()); } (ExecutionState::Closed, _) => { - return Err(PyRuntimeError::new_err("execution is closed")); + return Err(Error::ExecutionClosed.into()); } _ => { - return Err(PyRuntimeError::new_err( - "execution requires start before resume and can only start once", - )); + return Err(Error::ExecutionOutOfOrder.into()); } } match std::mem::replace(&mut execution.state, ExecutionState::Running) { @@ -116,9 +114,7 @@ impl Execution { drop(body); outcome.and_then(|(step, suspended)| { if suspended { - Err(PyRuntimeError::new_err( - "execution was closed while running", - )) + Err(Error::ExecutionClosedWhileRunning.into()) } else { Ok(step) } diff --git a/litellm-rust/crates/host-python/src/hooks/chain/adapter.rs b/litellm-rust/crates/host-python/src/hooks/chain/adapter.rs index b08a0e4ebcf..c7a5dad3858 100644 --- a/litellm-rust/crates/host-python/src/hooks/chain/adapter.rs +++ b/litellm-rust/crates/host-python/src/hooks/chain/adapter.rs @@ -6,7 +6,7 @@ use pyo3::{ types::PyDict, }; -use crate::{HookResume, HookStep, PythonCallEvent, PythonCallHooks, PythonOwned, missing_state}; +use crate::{Error, HookResume, HookStep, PythonCallEvent, PythonCallHooks, PythonOwned}; pub(super) enum ChainStep { Ready(T), @@ -110,7 +110,7 @@ impl ChainHooks for HookAdapter { result: PyResult>, ) -> PyResult>> { let Some(Continuation::Arguments(resume)) = self.continuation.take() else { - return Err(missing_state()); + return Err(Error::MissingState.into()); }; let step = resume(&mut self.hooks, py, result)?; Ok(self.step(step, Continuation::Arguments)) @@ -132,7 +132,7 @@ impl ChainHooks for HookAdapter { result: PyResult>, ) -> PyResult>> { let Some(Continuation::Wire(resume)) = self.continuation.take() else { - return Err(missing_state()); + return Err(Error::MissingState.into()); }; let step = resume(&mut self.hooks, py, result)?; Ok(self.step(step, Continuation::Wire)) @@ -154,7 +154,7 @@ impl ChainHooks for HookAdapter { result: PyResult>, ) -> PyResult>> { let Some(Continuation::Response(resume)) = self.continuation.take() else { - return Err(missing_state()); + return Err(Error::MissingState.into()); }; let step = resume(&mut self.hooks, py, result)?; Ok(self.step(step, Continuation::Response)) @@ -171,7 +171,7 @@ impl ChainHooks for HookAdapter { result: PyResult>, ) -> PyResult> { let Some(Continuation::Event(resume)) = self.continuation.take() else { - return Err(missing_state()); + return Err(Error::MissingState.into()); }; let step = resume(&mut self.hooks, py, result)?; Ok(self.step(step, Continuation::Event)) diff --git a/litellm-rust/crates/host-python/src/hooks/chain/dispatch.rs b/litellm-rust/crates/host-python/src/hooks/chain/dispatch.rs index 968345bde97..4939995cefa 100644 --- a/litellm-rust/crates/host-python/src/hooks/chain/dispatch.rs +++ b/litellm-rust/crates/host-python/src/hooks/chain/dispatch.rs @@ -10,9 +10,7 @@ use pyo3::{ types::PyDict, }; -use crate::{ - HookStep, PythonCallEvent, PythonCallHooks, PythonOwned, PythonRuntime, missing_state, -}; +use crate::{Error, HookStep, PythonCallEvent, PythonCallHooks, PythonOwned, PythonRuntime}; use super::adapter::{ChainHooks, ChainStep, HookAdapter}; @@ -72,7 +70,7 @@ impl HookChain { py: Python<'_>, result: PyResult>, ) -> PyResult>> { - let (index, context) = self.arguments.take().ok_or_else(missing_state)?; + let (index, context) = self.arguments.take().ok_or(Error::MissingState)?; let result = resume_unless_cancelled(py, result)?; let step = self.hooks[index].resume_arguments(py, result)?; self.arguments_step(py, index, step, context) @@ -113,7 +111,7 @@ impl HookChain { py: Python<'_>, result: PyResult>, ) -> PyResult>> { - let (index, context) = self.wire.take().ok_or_else(missing_state)?; + let (index, context) = self.wire.take().ok_or(Error::MissingState)?; let result = resume_unless_cancelled(py, result)?; let step = self.hooks[index].resume_wire(py, result)?; self.wire_step(py, index, step, &context) @@ -154,7 +152,7 @@ impl HookChain { py: Python<'_>, result: PyResult>, ) -> PyResult>> { - let (index, context) = self.response.take().ok_or_else(missing_state)?; + let (index, context) = self.response.take().ok_or(Error::MissingState)?; let result = resume_unless_cancelled(py, result)?; let step = self.hooks[index].resume_response(py, result)?; self.response_step(py, index, step, context) @@ -195,7 +193,7 @@ impl HookChain { py: Python<'_>, result: PyResult>, ) -> PyResult> { - let (index, event) = self.event.take().ok_or_else(missing_state)?; + let (index, event) = self.event.take().ok_or(Error::MissingState)?; let result = resume_unless_cancelled(py, result)?; let result = self.hooks[index].resume_event(py, result); let step = notification_result(py, is_terminal(&event), result)?; diff --git a/litellm-rust/crates/host-python/src/lib.rs b/litellm-rust/crates/host-python/src/lib.rs index 95bc03c2174..a83480be4a3 100644 --- a/litellm-rust/crates/host-python/src/lib.rs +++ b/litellm-rust/crates/host-python/src/lib.rs @@ -23,7 +23,7 @@ pub use argument::{effective_py_args, lookup, present}; pub use binding::PythonBinding; pub use conversion_cache::{FromPythonCache, ToPythonCache}; pub use driver::{CallOptions, run_call}; -pub use error::{InvokeError, missing_state}; +pub use error::{Error, InvokeError}; pub use file_reader::{FileContent, PythonFileReader, py_bytes}; pub use fork_gate::RuntimeAlreadyStarted; pub use gil::{PythonContext, attach_blocking, release_count, release_gil}; diff --git a/litellm-rust/crates/host-python/src/native.rs b/litellm-rust/crates/host-python/src/native.rs index 65d2ffc2c7a..4e6b59813f9 100644 --- a/litellm-rust/crates/host-python/src/native.rs +++ b/litellm-rust/crates/host-python/src/native.rs @@ -5,11 +5,10 @@ use futures_util::future::{AbortHandle, Abortable}; use litellm_host::call::HostedCompletion; use litellm_host::machine::{HostFailure, Machine, MachineStep}; use litellm_host::protocol::Protocol; -use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; use tokio::sync::Mutex; -use crate::missing_state; +use crate::Error; use crate::runtime::{poll_async_value, run_async_value, run_sync_value}; type NativeResult = Result< @@ -65,7 +64,7 @@ where py: Python<'_>, interruption: Option::Error>>, ) -> PyResult>> { - let state = Arc::clone(self.state.as_ref().ok_or_else(missing_state)?); + let state = Arc::clone(self.state.as_ref().ok_or(Error::MissingState)?); let future = async move { let mut state = state.lock().await; let result = match interruption { @@ -90,7 +89,7 @@ where run_async_value(py, async move { Abortable::new(future, registration) .await - .map_err(|_| PyRuntimeError::new_err("native execution closed"))? + .map_err(|_| Error::NativeExecutionClosed)? })? .unbind(), )) @@ -103,12 +102,12 @@ where pub(super) fn take_result(&self) -> PyResult> { self.state .as_ref() - .ok_or_else(missing_state)? + .ok_or(Error::MissingState)? .try_lock() - .map_err(|_| missing_state())? + .map_err(|_| Error::MissingState)? .result .take() - .ok_or_else(missing_state) + .ok_or_else(|| Error::MissingState.into()) .map(|result| { result.map(|step| match step { MachineStep::Suspended(op) => MachineStep::Suspended(op), diff --git a/litellm-rust/crates/host-python/src/runtime.rs b/litellm-rust/crates/host-python/src/runtime.rs index 9cb576dc156..c128242ef14 100644 --- a/litellm-rust/crates/host-python/src/runtime.rs +++ b/litellm-rust/crates/host-python/src/runtime.rs @@ -5,7 +5,7 @@ use std::task::{Context, Poll, Waker}; use std::time::Duration; use crate::fork_gate::{ForkGate, Refused, RuntimeAlreadyStarted}; -use crate::{Pythonized, panic_to_pyerr, release_gil}; +use crate::{Error, Pythonized, panic_to_pyerr, release_gil}; use futures_util::FutureExt; use pyo3::exceptions::PyRuntimeError; use pyo3::prelude::*; @@ -112,9 +112,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(Error::SyncInsideTokio.into()); } release_gil(py, move || runtime.block_on(wait_for_sync_result(future)))? } @@ -131,9 +129,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(Error::SyncInsideTokio.into()); } let result = release_gil(py, move || runtime.block_on(wait_for_sync_result(future)))?; diff --git a/litellm-rust/crates/host-python/tests/driver.rs b/litellm-rust/crates/host-python/tests/driver.rs index 51a3653c2e2..fc4a8ee726c 100644 --- a/litellm-rust/crates/host-python/tests/driver.rs +++ b/litellm-rust/crates/host-python/tests/driver.rs @@ -12,8 +12,8 @@ use litellm_host::{ protocol::Reply, }; use litellm_host_python::{ - CallOptions, HookChain, InvokeError, PythonBinding, PythonHostCalls, PythonOwned, - PythonRuntime, missing_state, run_call, + CallOptions, Error as HostError, HookChain, InvokeError, PythonBinding, PythonHostCalls, + PythonOwned, PythonRuntime, run_call, }; use pyo3::{ exceptions::{ @@ -818,7 +818,7 @@ fn cancellation_ends_the_call_without_terminal_dispatch(#[from(interpreter)] _py match chunk {} } fn encode_response(&mut self, _: Python<'_>, _: String) -> PyResult> { - Err(missing_state()) + Err(HostError::MissingState.into()) } fn map_error(&self, _: Python<'_>, error: Error) -> PyResult { self.0.push("classify"); @@ -835,7 +835,7 @@ fn cancellation_ends_the_call_without_terminal_dispatch(#[from(interpreter)] _py _: Python<'_>, _: (&'static str, Reply), ) -> Result<(), InvokeError> { - Err(missing_state().into()) + Err(PyErr::from(HostError::MissingState).into()) } } diff --git a/litellm-rust/crates/python-bridge/src/routes/mod.rs b/litellm-rust/crates/python-bridge/src/routes/mod.rs index 416b94db55b..31885694b82 100644 --- a/litellm-rust/crates/python-bridge/src/routes/mod.rs +++ b/litellm-rust/crates/python-bridge/src/routes/mod.rs @@ -11,10 +11,10 @@ pub(crate) mod traces; use litellm_callbacks_legacy_python::{LegacyLogging, LoggingOperation, PublicCall}; use litellm_host::{call::HostedCompletion, machine::Machine, protocol::Protocol}; +use litellm_host_python::{Error as HostError, present}; use litellm_host_python::{ HookChain, PythonBinding, PythonCallHooks, PythonHostCalls, effective_py_args, }; -use litellm_host_python::{missing_state, present}; use pyo3::{ gc::{PyTraverseError, PyVisit}, prelude::*, @@ -64,7 +64,9 @@ impl RequestView { } fn dicts(&self) -> PyResult<&RequestDicts> { - self.0.as_ref().ok_or_else(missing_state) + self.0 + .as_ref() + .ok_or_else(|| HostError::MissingState.into()) } pub(crate) fn resolve( @@ -72,7 +74,7 @@ impl RequestView { py: Python<'_>, arguments: &Bound<'_, PyDict>, ) -> PyResult<()> { - let dicts = self.0.as_mut().ok_or_else(missing_state)?; + let dicts = self.0.as_mut().ok_or(HostError::MissingState)?; dicts.request = effective_py_args(dicts.base.bind(py), arguments)?.unbind(); Ok(()) } diff --git a/litellm-rust/crates/python-bridge/src/routes/ocr/host.rs b/litellm-rust/crates/python-bridge/src/routes/ocr/host.rs index dd3c648cc3e..4c8469cc019 100644 --- a/litellm-rust/crates/python-bridge/src/routes/ocr/host.rs +++ b/litellm-rust/crates/python-bridge/src/routes/ocr/host.rs @@ -1,5 +1,5 @@ use litellm_auth::ResolvedCredential; -use litellm_host_python::{InvokeError, PythonBinding, missing_state}; +use litellm_host_python::{Error as HostError, InvokeError, PythonBinding}; use litellm_host_python::{PythonHostCalls, PythonOwned}; use litellm_inference_ocr::route::{Ocr, OcrCall, OcrOp}; use litellm_llms::base_llm::ocr::error::Error; @@ -42,7 +42,7 @@ impl OcrPythonHost { fn handles(&self) -> PyResult<&OcrHostHandles> { match &self.data { OcrHostData::Projected(handles) => Ok(handles), - _ => Err(missing_state()), + _ => Err(HostError::MissingState.into()), } } @@ -50,13 +50,13 @@ impl OcrPythonHost { self.handles()? .azure_ad_token_provider .as_ref() - .ok_or_else(missing_state)? + .ok_or(HostError::MissingState)? .acquire(py) } fn projection(&mut self, py: Python<'_>, arguments: &Bound<'_, PyDict>) -> PyResult { let OcrHostData::Unprojected = self.data else { - return Err(missing_state()); + return Err(HostError::MissingState.into()); }; self.view.resolve(py, arguments)?; let (request, handles) = project_request(self.view.request(py)?, arguments)?;