Skip to main content

autoagents_core/agent/
error.rs

1use autoagents_llm::error::LLMError;
2#[cfg(not(target_arch = "wasm32"))]
3use ractor::SpawnErr;
4use std::fmt::Debug;
5use thiserror::Error;
6
7/// Error type for RunnableAgent operations
8#[derive(Debug, Error)]
9pub enum RunnableAgentError {
10    /// Error from the agent executor
11    #[error("Agent execution failed: {0}")]
12    ExecutorError(String),
13
14    /// LLM-specific error surfaced from the executor chain
15    #[error("LLM error: {0}")]
16    LLMError(#[from] LLMError),
17
18    /// Error during task processing
19    #[error("Task processing failed: {0}")]
20    TaskError(String),
21
22    /// Error when agent is not found
23    #[error("Agent not found: {0}")]
24    AgentNotFound(uuid::Uuid),
25
26    /// Error during agent initialization
27    #[error("Agent initialization failed: {0}")]
28    InitializationError(String),
29
30    /// Error when sending events
31    #[error("Failed to send event: {0}")]
32    EventSendError(String),
33
34    /// Error from agent state operations
35    #[error("Agent state error: {0}")]
36    StateError(String),
37
38    /// Error from agent state operations
39    #[error("Downcast task error")]
40    DowncastTaskError,
41
42    /// Error during serialization/deserialization
43    #[error("Serialization error: {0}")]
44    SerializationError(String),
45
46    /// Error during serialization/deserialization
47    #[error("EmptyTx")]
48    EmptyTx,
49
50    /// Abort the Execution
51    #[error("Abort the execution")]
52    Abort,
53
54    /// Generic error wrapper for any std::error::Error
55    #[error(transparent)]
56    Other(#[from] Box<dyn std::error::Error + Send + Sync>),
57}
58
59impl RunnableAgentError {
60    /// Create an executor error from any error type
61    pub fn executor_error(error: impl std::error::Error) -> Self {
62        Self::ExecutorError(error.to_string())
63    }
64
65    /// Create a task error
66    pub fn task_error(msg: impl Into<String>) -> Self {
67        Self::TaskError(msg.into())
68    }
69
70    /// Create an event send error
71    pub fn event_send_error(error: impl std::error::Error) -> Self {
72        Self::EventSendError(error.to_string())
73    }
74}
75
76impl From<crate::agent::executor::turn_engine::TurnEngineError> for RunnableAgentError {
77    fn from(error: crate::agent::executor::turn_engine::TurnEngineError) -> Self {
78        match error {
79            crate::agent::executor::turn_engine::TurnEngineError::LLMError(err) => {
80                RunnableAgentError::LLMError(err)
81            }
82            crate::agent::executor::turn_engine::TurnEngineError::Aborted => {
83                RunnableAgentError::Abort
84            }
85            crate::agent::executor::turn_engine::TurnEngineError::Other(err) => {
86                RunnableAgentError::ExecutorError(err)
87            }
88        }
89    }
90}
91
92impl From<crate::agent::prebuilt::executor::BasicExecutorError> for RunnableAgentError {
93    fn from(error: crate::agent::prebuilt::executor::BasicExecutorError) -> Self {
94        match error {
95            crate::agent::prebuilt::executor::BasicExecutorError::LLMError(err) => {
96                RunnableAgentError::LLMError(err)
97            }
98            crate::agent::prebuilt::executor::BasicExecutorError::Other(err) => {
99                RunnableAgentError::ExecutorError(err)
100            }
101        }
102    }
103}
104
105impl From<crate::agent::prebuilt::executor::ReActExecutorError> for RunnableAgentError {
106    fn from(error: crate::agent::prebuilt::executor::ReActExecutorError) -> Self {
107        match error {
108            crate::agent::prebuilt::executor::ReActExecutorError::LLMError(err) => {
109                RunnableAgentError::LLMError(err)
110            }
111            other => RunnableAgentError::ExecutorError(other.to_string()),
112        }
113    }
114}
115
116/// Specific conversion for tokio mpsc send errors
117#[cfg(not(target_arch = "wasm32"))]
118impl<T> From<tokio::sync::mpsc::error::SendError<T>> for RunnableAgentError
119where
120    T: Debug + Send + 'static,
121{
122    fn from(error: tokio::sync::mpsc::error::SendError<T>) -> Self {
123        Self::EventSendError(error.to_string())
124    }
125}
126
127#[derive(Debug, thiserror::Error)]
128pub enum AgentBuildError {
129    #[error("Build Failure")]
130    BuildFailure(String),
131
132    #[cfg(not(target_arch = "wasm32"))]
133    #[error("SpawnError")]
134    SpawnError(#[from] SpawnErr),
135}
136
137impl AgentBuildError {
138    pub fn build_failure(msg: impl Into<String>) -> Self {
139        Self::BuildFailure(msg.into())
140    }
141}
142
143#[derive(Error, Debug)]
144pub enum AgentResultError {
145    #[error("No output available in result")]
146    NoOutput,
147
148    #[error("Failed to deserialize executor output: {0}")]
149    DeserializationError(#[from] serde_json::Error),
150
151    #[error("Agent output extraction error: {0}")]
152    AgentOutputError(String),
153}
154
155impl AgentResultError {
156    pub fn agent_output_error(msg: impl Into<String>) -> Self {
157        Self::AgentOutputError(msg.into())
158    }
159}
160
161#[cfg(test)]
162mod tests {
163    use super::*;
164    use crate::agent::prebuilt::executor::{BasicExecutorError, ReActExecutorError};
165    use autoagents_llm::error::GuardrailPhase;
166    use tokio::sync::mpsc;
167
168    #[test]
169    fn test_runnable_agent_error_display() {
170        let error = RunnableAgentError::ExecutorError("Test error".to_string());
171        assert_eq!(error.to_string(), "Agent execution failed: Test error");
172
173        let error = RunnableAgentError::TaskError("Task failed".to_string());
174        assert_eq!(error.to_string(), "Task processing failed: Task failed");
175
176        let error = RunnableAgentError::AgentNotFound(uuid::Uuid::new_v4());
177        assert!(error.to_string().contains("Agent not found:"));
178    }
179
180    #[test]
181    fn test_runnable_agent_error_constructors() {
182        let error = RunnableAgentError::executor_error(std::io::Error::other("IO error"));
183        assert!(matches!(error, RunnableAgentError::ExecutorError(_)));
184
185        let error = RunnableAgentError::task_error("Custom task error");
186        assert!(matches!(error, RunnableAgentError::TaskError(_)));
187
188        let error = RunnableAgentError::event_send_error(std::io::Error::new(
189            std::io::ErrorKind::BrokenPipe,
190            "Send failed",
191        ));
192        assert!(matches!(error, RunnableAgentError::EventSendError(_)));
193    }
194
195    #[tokio::test]
196    async fn test_runnable_agent_error_from_mpsc_send_error() {
197        let (_tx, rx) = mpsc::channel::<String>(1);
198        drop(rx); // Close receiver to cause send error
199
200        let (tx, _rx) = mpsc::channel::<String>(1);
201        drop(tx); // This will cause an error when we try to send
202
203        // Create a send error manually for testing
204        let result: Result<(), mpsc::error::SendError<String>> =
205            Err(mpsc::error::SendError("test message".to_string()));
206
207        if let Err(send_error) = result {
208            let agent_error: RunnableAgentError = send_error.into();
209            assert!(matches!(agent_error, RunnableAgentError::EventSendError(_)));
210        }
211    }
212
213    #[test]
214    fn test_agent_build_error_display() {
215        let error = AgentBuildError::BuildFailure("Failed to build agent".to_string());
216        assert_eq!(error.to_string(), "Build Failure");
217
218        let error = AgentBuildError::build_failure("Custom build failure");
219        assert!(matches!(error, AgentBuildError::BuildFailure(_)));
220    }
221
222    #[test]
223    fn test_agent_result_error_display() {
224        let error = AgentResultError::NoOutput;
225        assert_eq!(error.to_string(), "No output available in result");
226
227        let error = AgentResultError::AgentOutputError("Custom output error".to_string());
228        assert_eq!(
229            error.to_string(),
230            "Agent output extraction error: Custom output error"
231        );
232
233        let error = AgentResultError::agent_output_error("Helper constructor error");
234        assert!(matches!(error, AgentResultError::AgentOutputError(_)));
235    }
236
237    #[test]
238    fn test_agent_result_error_from_json_error() {
239        let invalid_json = "{ invalid json }";
240        let json_error: Result<serde_json::Value, serde_json::Error> =
241            serde_json::from_str(invalid_json);
242
243        if let Err(json_err) = json_error {
244            let agent_error: AgentResultError = json_err.into();
245            assert!(matches!(
246                agent_error,
247                AgentResultError::DeserializationError(_)
248            ));
249        }
250    }
251
252    #[test]
253    fn test_error_debug_formatting() {
254        let error = RunnableAgentError::InitializationError("Init failed".to_string());
255        let debug_str = format!("{error:?}");
256        assert!(debug_str.contains("InitializationError"));
257        assert!(debug_str.contains("Init failed"));
258    }
259
260    #[test]
261    fn test_from_llm_error_preserves_typed_llm_error_direct() {
262        let source = LLMError::GuardrailBlocked {
263            phase: GuardrailPhase::Input,
264            guard: "prompt-injection".to_string().into(),
265            rule_id: "prompt_injection_detected".to_string().into(),
266            category: "prompt_injection".to_string().into(),
267            severity: "high".to_string().into(),
268            message: "detected suspicious instruction pattern: jailbreak"
269                .to_string()
270                .into(),
271        };
272
273        let converted: RunnableAgentError = source.into();
274        assert!(matches!(
275            converted,
276            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
277        ));
278    }
279
280    #[test]
281    fn test_from_basic_executor_preserves_typed_llm_error() {
282        let wrapped = BasicExecutorError::from(LLMError::GuardrailBlocked {
283            phase: GuardrailPhase::Input,
284            guard: "prompt-injection".to_string().into(),
285            rule_id: "prompt_injection_detected".to_string().into(),
286            category: "prompt_injection".to_string().into(),
287            severity: "high".to_string().into(),
288            message: "detected suspicious instruction pattern: jailbreak"
289                .to_string()
290                .into(),
291        });
292
293        let converted: RunnableAgentError = wrapped.into();
294        assert!(matches!(
295            converted,
296            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
297        ));
298    }
299
300    #[test]
301    fn test_from_react_executor_preserves_typed_llm_error() {
302        let wrapped = ReActExecutorError::from(LLMError::GuardrailBlocked {
303            phase: GuardrailPhase::Input,
304            guard: "prompt-injection".to_string().into(),
305            rule_id: "prompt_injection_detected".to_string().into(),
306            category: "prompt_injection".to_string().into(),
307            severity: "high".to_string().into(),
308            message: "detected suspicious instruction pattern: jailbreak"
309                .to_string()
310                .into(),
311        });
312
313        let converted: RunnableAgentError = wrapped.into();
314        assert!(matches!(
315            converted,
316            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
317        ));
318    }
319}