autoagents_core/agent/executor/
mod.rs1pub 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#[derive(Debug)]
19pub enum TurnResult<T> {
20 Continue(Option<T>),
22 Complete(T),
24}
25
26#[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#[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 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 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 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}