Skip to main content

autoagents_core/agent/executor/
mod.rs

1pub mod event_helper;
2pub mod memory_helper;
3pub mod memory_policy;
4pub mod tool_processor;
5pub mod turn_engine;
6
7use crate::agent::context::Context;
8use crate::agent::task::Task;
9use crate::utils::BoxRuntimeStream;
10use async_trait::async_trait;
11use serde::Serialize;
12use serde::de::DeserializeOwned;
13use std::error::Error;
14use std::fmt::Debug;
15use std::sync::Arc;
16
17/// Result of processing a single turn in the agent's execution
18#[derive(Debug)]
19pub enum TurnResult<T> {
20    /// Continue processing with optional intermediate data
21    Continue(Option<T>),
22    /// Final result obtained
23    Complete(T),
24}
25
26/// Configuration for executors
27#[derive(Debug, Clone)]
28pub struct ExecutorConfig {
29    pub max_turns: usize,
30}
31
32impl Default for ExecutorConfig {
33    fn default() -> Self {
34        Self { max_turns: 10 }
35    }
36}
37
38/// Base trait for agent execution strategies
39///
40/// Executors are responsible for implementing the specific execution logic
41/// for agents, such as ReAct loops, chain-of-thought, or custom patterns.
42#[cfg_attr(all(target_arch = "wasm32", target_os = "wasi"), async_trait(?Send))]
43#[cfg_attr(not(all(target_arch = "wasm32", target_os = "wasi")), async_trait)]
44pub trait AgentExecutor: Send + Sync + 'static {
45    type Output: Serialize + DeserializeOwned + Clone + Send + Sync + Debug;
46    type Error: Error + Send + Sync + 'static;
47
48    fn config(&self) -> ExecutorConfig;
49
50    async fn execute(
51        &self,
52        task: &Task,
53        context: Arc<Context>,
54    ) -> Result<Self::Output, Self::Error>;
55
56    async fn execute_stream(
57        &self,
58        task: &Task,
59        context: Arc<Context>,
60    ) -> Result<BoxRuntimeStream<Result<Self::Output, Self::Error>>, Self::Error> {
61        // Default fallback to self.execute with final result as a single-item stream
62        let context_clone = context.clone();
63        let result = self.execute(task, context_clone).await;
64        let stream = futures::stream::iter(vec![result]);
65        Ok(Box::pin(stream))
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::agent::context::Context;
73    use crate::agent::task::Task;
74    use async_trait::async_trait;
75    use autoagents_llm::{
76        LLMProvider, ToolCall,
77        chat::{ChatMessage, ChatProvider, ChatResponse, StructuredOutputFormat},
78        completion::{CompletionProvider, CompletionRequest, CompletionResponse},
79        embedding::EmbeddingProvider,
80        error::LLMError,
81        models::ModelsProvider,
82    };
83    use futures::stream;
84    use serde::{Deserialize, Serialize};
85    use serde_json::Value;
86    use std::sync::Arc;
87    use tokio::sync::mpsc;
88
89    #[derive(Debug, Clone, Serialize, Deserialize)]
90    struct TestOutput {
91        message: String,
92    }
93
94    impl From<TestOutput> for Value {
95        fn from(output: TestOutput) -> Self {
96            serde_json::to_value(output).unwrap_or(Value::Null)
97        }
98    }
99
100    #[derive(Debug, thiserror::Error)]
101    enum TestError {
102        #[error("Test error: {0}")]
103        TestError(String),
104    }
105
106    struct MockExecutor {
107        should_fail: bool,
108        max_turns: usize,
109    }
110
111    impl MockExecutor {
112        fn new(should_fail: bool) -> Self {
113            Self {
114                should_fail,
115                max_turns: 5,
116            }
117        }
118
119        fn with_max_turns(max_turns: usize) -> Self {
120            Self {
121                should_fail: false,
122                max_turns,
123            }
124        }
125    }
126
127    #[async_trait]
128    impl AgentExecutor for MockExecutor {
129        type Output = TestOutput;
130        type Error = TestError;
131
132        fn config(&self) -> ExecutorConfig {
133            ExecutorConfig {
134                max_turns: self.max_turns,
135            }
136        }
137
138        async fn execute(
139            &self,
140            task: &Task,
141            _context: Arc<Context>,
142        ) -> Result<Self::Output, Self::Error> {
143            if self.should_fail {
144                return Err(TestError::TestError("Mock execution failed".to_string()));
145            }
146
147            Ok(TestOutput {
148                message: format!("Processed: {}", task.prompt),
149            })
150        }
151        async fn execute_stream(
152            &self,
153            task: &Task,
154            context: Arc<Context>,
155        ) -> Result<BoxRuntimeStream<Result<Self::Output, Self::Error>>, Self::Error> {
156            // Use the default implementation from the trait
157            let context_clone = context.clone();
158            let result = self.execute(task, context_clone).await;
159            let stream = stream::once(async move { result });
160            Ok(Box::pin(stream))
161        }
162    }
163
164    // Mock LLM Provider
165    struct MockLLMProvider;
166
167    #[async_trait]
168    impl ChatProvider for MockLLMProvider {
169        async fn chat(
170            &self,
171            _messages: &[ChatMessage],
172            _json_schema: Option<StructuredOutputFormat>,
173        ) -> Result<Box<dyn ChatResponse>, LLMError> {
174            Ok(Box::new(MockChatResponse {
175                text: Some("Mock response".to_string()),
176            }))
177        }
178        async fn chat_with_tools(
179            &self,
180            _messages: &[ChatMessage],
181            _tools: Option<&[autoagents_llm::chat::Tool]>,
182            _json_schema: Option<StructuredOutputFormat>,
183        ) -> Result<Box<dyn ChatResponse>, LLMError> {
184            Ok(Box::new(MockChatResponse {
185                text: Some("Mock response".to_string()),
186            }))
187        }
188    }
189
190    #[async_trait]
191    impl CompletionProvider for MockLLMProvider {
192        async fn complete(
193            &self,
194            _req: &CompletionRequest,
195            _json_schema: Option<StructuredOutputFormat>,
196        ) -> Result<CompletionResponse, LLMError> {
197            Ok(CompletionResponse {
198                text: "Mock completion".to_string(),
199            })
200        }
201    }
202
203    #[async_trait]
204    impl EmbeddingProvider for MockLLMProvider {
205        async fn embed(&self, _text: Vec<String>) -> Result<Vec<Vec<f32>>, LLMError> {
206            Ok(vec![vec![0.1, 0.2, 0.3]])
207        }
208    }
209
210    #[async_trait]
211    impl ModelsProvider for MockLLMProvider {}
212
213    impl LLMProvider for MockLLMProvider {}
214
215    struct MockChatResponse {
216        text: Option<String>,
217    }
218
219    impl ChatResponse for MockChatResponse {
220        fn text(&self) -> Option<String> {
221            self.text.clone()
222        }
223
224        fn tool_calls(&self) -> Option<Vec<ToolCall>> {
225            None
226        }
227    }
228
229    impl std::fmt::Debug for MockChatResponse {
230        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
231            write!(f, "MockChatResponse")
232        }
233    }
234
235    impl std::fmt::Display for MockChatResponse {
236        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
237            write!(f, "{}", self.text.as_deref().unwrap_or(""))
238        }
239    }
240
241    #[test]
242    fn test_executor_config_default() {
243        let config = ExecutorConfig::default();
244        assert_eq!(config.max_turns, 10);
245    }
246
247    #[test]
248    fn test_executor_config_custom() {
249        let config = ExecutorConfig { max_turns: 5 };
250        assert_eq!(config.max_turns, 5);
251    }
252
253    #[test]
254    fn test_executor_config_clone() {
255        let config = ExecutorConfig { max_turns: 15 };
256        let cloned = config.clone();
257        assert_eq!(config.max_turns, cloned.max_turns);
258    }
259
260    #[test]
261    fn test_executor_config_debug() {
262        let config = ExecutorConfig { max_turns: 20 };
263        let debug_str = format!("{config:?}");
264        assert!(debug_str.contains("ExecutorConfig"));
265        assert!(debug_str.contains("20"));
266    }
267
268    #[test]
269    fn test_turn_result_continue() {
270        let result = TurnResult::<String>::Continue(Some("partial".to_string()));
271        match result {
272            TurnResult::Continue(Some(data)) => assert_eq!(data, "partial"),
273            _ => panic!("Expected Continue variant"),
274        }
275    }
276
277    #[test]
278    fn test_turn_result_continue_none() {
279        let result = TurnResult::<String>::Continue(None);
280        match result {
281            TurnResult::Continue(None) => {}
282            _ => panic!("Expected Continue(None) variant"),
283        }
284    }
285
286    #[test]
287    fn test_turn_result_complete() {
288        let result = TurnResult::Complete("final".to_string());
289        match result {
290            TurnResult::Complete(data) => assert_eq!(data, "final"),
291            _ => panic!("Expected Complete variant"),
292        }
293    }
294
295    #[test]
296    fn test_turn_result_debug() {
297        let result = TurnResult::Complete("test".to_string());
298        let debug_str = format!("{result:?}");
299        assert!(debug_str.contains("Complete"));
300        assert!(debug_str.contains("test"));
301    }
302
303    #[tokio::test]
304    async fn test_mock_executor_success() {
305        let executor = MockExecutor::new(false);
306        let llm = Arc::new(MockLLMProvider);
307        let task = Task::new("test task");
308        let (tx_event, _rx_event) = mpsc::channel(100);
309        let context = Context::new(llm, Some(tx_event));
310
311        let result = executor.execute(&task, Arc::new(context)).await;
312
313        assert!(result.is_ok());
314        let output = result.unwrap();
315        assert_eq!(output.message, "Processed: test task");
316    }
317
318    #[tokio::test]
319    async fn test_mock_executor_failure() {
320        let executor = MockExecutor::new(true);
321        let llm = Arc::new(MockLLMProvider);
322        let task = Task::new("test task");
323        let (tx_event, _rx_event) = mpsc::channel(100);
324        let context = Context::new(llm, Some(tx_event));
325
326        let result = executor.execute(&task, Arc::new(context)).await;
327
328        assert!(result.is_err());
329        let error = result.unwrap_err();
330        assert_eq!(error.to_string(), "Test error: Mock execution failed");
331    }
332
333    #[test]
334    fn test_mock_executor_config() {
335        let executor = MockExecutor::with_max_turns(3);
336        let config = executor.config();
337        assert_eq!(config.max_turns, 3);
338    }
339
340    #[test]
341    fn test_mock_executor_config_default() {
342        let executor = MockExecutor::new(false);
343        let config = executor.config();
344        assert_eq!(config.max_turns, 5);
345    }
346
347    #[test]
348    fn test_test_output_serialization() {
349        let output = TestOutput {
350            message: "test message".to_string(),
351        };
352        let serialized = serde_json::to_string(&output).unwrap();
353        assert!(serialized.contains("test message"));
354    }
355
356    #[test]
357    fn test_test_output_deserialization() {
358        let json = r#"{"message":"test message"}"#;
359        let output: TestOutput = serde_json::from_str(json).unwrap();
360        assert_eq!(output.message, "test message");
361    }
362
363    #[test]
364    fn test_test_output_clone() {
365        let output = TestOutput {
366            message: "original".to_string(),
367        };
368        let cloned = output.clone();
369        assert_eq!(output.message, cloned.message);
370    }
371
372    #[test]
373    fn test_test_output_debug() {
374        let output = TestOutput {
375            message: "debug test".to_string(),
376        };
377        let debug_str = format!("{output:?}");
378        assert!(debug_str.contains("TestOutput"));
379        assert!(debug_str.contains("debug test"));
380    }
381
382    #[test]
383    fn test_test_output_into_value() {
384        let output = TestOutput {
385            message: "value test".to_string(),
386        };
387        let value: Value = output.into();
388        assert_eq!(value["message"], "value test");
389    }
390
391    #[test]
392    fn test_test_error_display() {
393        let error = TestError::TestError("display test".to_string());
394        assert_eq!(error.to_string(), "Test error: display test");
395    }
396
397    #[test]
398    fn test_test_error_debug() {
399        let error = TestError::TestError("debug test".to_string());
400        let debug_str = format!("{error:?}");
401        assert!(debug_str.contains("TestError"));
402        assert!(debug_str.contains("debug test"));
403    }
404
405    #[test]
406    fn test_test_error_source() {
407        let error = TestError::TestError("source test".to_string());
408        assert!(error.source().is_none());
409    }
410}