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#[cfg(feature = "codeact")]
117impl From<crate::agent::prebuilt::executor::CodeActExecutorError> for RunnableAgentError {
118    fn from(error: crate::agent::prebuilt::executor::CodeActExecutorError) -> Self {
119        match error {
120            crate::agent::prebuilt::executor::CodeActExecutorError::LLMError(err) => {
121                RunnableAgentError::LLMError(err)
122            }
123            other => RunnableAgentError::ExecutorError(other.to_string()),
124        }
125    }
126}
127
128/// Specific conversion for tokio mpsc send errors
129#[cfg(not(target_arch = "wasm32"))]
130impl<T> From<tokio::sync::mpsc::error::SendError<T>> for RunnableAgentError
131where
132    T: Debug + Send + 'static,
133{
134    fn from(error: tokio::sync::mpsc::error::SendError<T>) -> Self {
135        Self::EventSendError(error.to_string())
136    }
137}
138
139#[derive(Debug, thiserror::Error)]
140pub enum AgentBuildError {
141    #[error("Build Failure")]
142    BuildFailure(String),
143
144    #[cfg(not(target_arch = "wasm32"))]
145    #[error("SpawnError")]
146    SpawnError(#[from] SpawnErr),
147}
148
149impl AgentBuildError {
150    pub fn build_failure(msg: impl Into<String>) -> Self {
151        Self::BuildFailure(msg.into())
152    }
153}
154
155#[derive(Error, Debug)]
156pub enum AgentResultError {
157    #[error("No output available in result")]
158    NoOutput,
159
160    #[error("Failed to deserialize executor output: {0}")]
161    DeserializationError(#[from] serde_json::Error),
162
163    #[error("Agent output extraction error: {0}")]
164    AgentOutputError(String),
165}
166
167impl AgentResultError {
168    pub fn agent_output_error(msg: impl Into<String>) -> Self {
169        Self::AgentOutputError(msg.into())
170    }
171}
172
173#[cfg(test)]
174mod tests {
175    use super::*;
176    use crate::agent::prebuilt::executor::{BasicExecutorError, ReActExecutorError};
177    use autoagents_llm::error::GuardrailPhase;
178    use tokio::sync::mpsc;
179
180    #[test]
181    fn test_runnable_agent_error_display() {
182        let error = RunnableAgentError::ExecutorError("Test error".to_string());
183        assert_eq!(error.to_string(), "Agent execution failed: Test error");
184
185        let error = RunnableAgentError::TaskError("Task failed".to_string());
186        assert_eq!(error.to_string(), "Task processing failed: Task failed");
187
188        let error = RunnableAgentError::AgentNotFound(uuid::Uuid::new_v4());
189        assert!(error.to_string().contains("Agent not found:"));
190    }
191
192    #[test]
193    fn test_runnable_agent_error_constructors() {
194        let error = RunnableAgentError::executor_error(std::io::Error::other("IO error"));
195        assert!(matches!(error, RunnableAgentError::ExecutorError(_)));
196
197        let error = RunnableAgentError::task_error("Custom task error");
198        assert!(matches!(error, RunnableAgentError::TaskError(_)));
199
200        let error = RunnableAgentError::event_send_error(std::io::Error::new(
201            std::io::ErrorKind::BrokenPipe,
202            "Send failed",
203        ));
204        assert!(matches!(error, RunnableAgentError::EventSendError(_)));
205    }
206
207    #[tokio::test]
208    async fn test_runnable_agent_error_from_mpsc_send_error() {
209        let (_tx, rx) = mpsc::channel::<String>(1);
210        drop(rx); // Close receiver to cause send error
211
212        let (tx, _rx) = mpsc::channel::<String>(1);
213        drop(tx); // This will cause an error when we try to send
214
215        // Create a send error manually for testing
216        let result: Result<(), mpsc::error::SendError<String>> =
217            Err(mpsc::error::SendError("test message".to_string()));
218
219        if let Err(send_error) = result {
220            let agent_error: RunnableAgentError = send_error.into();
221            assert!(matches!(agent_error, RunnableAgentError::EventSendError(_)));
222        }
223    }
224
225    #[test]
226    fn test_agent_build_error_display() {
227        let error = AgentBuildError::BuildFailure("Failed to build agent".to_string());
228        assert_eq!(error.to_string(), "Build Failure");
229
230        let error = AgentBuildError::build_failure("Custom build failure");
231        assert!(matches!(error, AgentBuildError::BuildFailure(_)));
232    }
233
234    #[test]
235    fn test_agent_result_error_display() {
236        let error = AgentResultError::NoOutput;
237        assert_eq!(error.to_string(), "No output available in result");
238
239        let error = AgentResultError::AgentOutputError("Custom output error".to_string());
240        assert_eq!(
241            error.to_string(),
242            "Agent output extraction error: Custom output error"
243        );
244
245        let error = AgentResultError::agent_output_error("Helper constructor error");
246        assert!(matches!(error, AgentResultError::AgentOutputError(_)));
247    }
248
249    #[test]
250    fn test_agent_result_error_from_json_error() {
251        let invalid_json = "{ invalid json }";
252        let json_error: Result<serde_json::Value, serde_json::Error> =
253            serde_json::from_str(invalid_json);
254
255        if let Err(json_err) = json_error {
256            let agent_error: AgentResultError = json_err.into();
257            assert!(matches!(
258                agent_error,
259                AgentResultError::DeserializationError(_)
260            ));
261        }
262    }
263
264    #[test]
265    fn test_error_debug_formatting() {
266        let error = RunnableAgentError::InitializationError("Init failed".to_string());
267        let debug_str = format!("{error:?}");
268        assert!(debug_str.contains("InitializationError"));
269        assert!(debug_str.contains("Init failed"));
270    }
271
272    #[test]
273    fn test_from_llm_error_preserves_typed_llm_error_direct() {
274        let source = LLMError::GuardrailBlocked {
275            phase: GuardrailPhase::Input,
276            guard: "prompt-injection".to_string().into(),
277            rule_id: "prompt_injection_detected".to_string().into(),
278            category: "prompt_injection".to_string().into(),
279            severity: "high".to_string().into(),
280            message: "detected suspicious instruction pattern: jailbreak"
281                .to_string()
282                .into(),
283        };
284
285        let converted: RunnableAgentError = source.into();
286        assert!(matches!(
287            converted,
288            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
289        ));
290    }
291
292    #[test]
293    fn test_from_basic_executor_preserves_typed_llm_error() {
294        let wrapped = BasicExecutorError::from(LLMError::GuardrailBlocked {
295            phase: GuardrailPhase::Input,
296            guard: "prompt-injection".to_string().into(),
297            rule_id: "prompt_injection_detected".to_string().into(),
298            category: "prompt_injection".to_string().into(),
299            severity: "high".to_string().into(),
300            message: "detected suspicious instruction pattern: jailbreak"
301                .to_string()
302                .into(),
303        });
304
305        let converted: RunnableAgentError = wrapped.into();
306        assert!(matches!(
307            converted,
308            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
309        ));
310    }
311
312    #[test]
313    fn test_from_react_executor_preserves_typed_llm_error() {
314        let wrapped = ReActExecutorError::from(LLMError::GuardrailBlocked {
315            phase: GuardrailPhase::Input,
316            guard: "prompt-injection".to_string().into(),
317            rule_id: "prompt_injection_detected".to_string().into(),
318            category: "prompt_injection".to_string().into(),
319            severity: "high".to_string().into(),
320            message: "detected suspicious instruction pattern: jailbreak"
321                .to_string()
322                .into(),
323        });
324
325        let converted: RunnableAgentError = wrapped.into();
326        assert!(matches!(
327            converted,
328            RunnableAgentError::LLMError(LLMError::GuardrailBlocked { .. })
329        ));
330    }
331}