Skip to main content

lc_agents/
base.rs

1// lc-agents/src/base.rs
2//! Agent base traits and executor implementation.
3
4use super::types::{AgentAction, AgentFinish, AgentOutput, AgentStep};
5use super::streaming::state::AgentStreamEvent;
6use async_trait::async_trait;
7use lc_callbacks::{CallbackManager, RunTree, RunType};
8use lc_core::tools::BaseTool;
9use lc_memory::BaseMemory;
10use serde_json::json;
11use std::collections::HashMap;
12use std::pin::Pin;
13use std::sync::Arc;
14use futures_util::Stream;
15
16/// Agent error types.
17#[derive(Debug, thiserror::Error)]
18pub enum AgentError {
19    /// Output parsing error.
20    #[error("Output parsing error: {0}")]
21    OutputParsingError(String),
22
23    /// Tool not found.
24    #[error("Tool not found: {0}")]
25    ToolNotFound(String),
26
27    /// Tool execution error.
28    #[error("Tool execution error: {0}")]
29    ToolExecutionError(String),
30
31    /// Max iterations reached.
32    #[error("Max iterations reached")]
33    MaxIterationsReached,
34
35    /// Other error.
36    #[error("Agent error: {0}")]
37    Other(String),
38}
39
40/// Base Agent trait.
41///
42/// Defines the core interface for agents. Agent is responsible for planning,
43/// not execution. Execution is handled by AgentExecutor.
44#[async_trait]
45pub trait BaseAgent: Send + Sync {
46    /// Plans the next action.
47    ///
48    /// # Arguments
49    /// * `intermediate_steps` - History of executed steps.
50    /// * `inputs` - User input.
51    ///
52    /// # Returns
53    /// * `AgentOutput::Action` - Action to execute.
54    /// * `AgentOutput::Finish` - Final answer.
55    async fn plan(
56        &self,
57        intermediate_steps: &[AgentStep],
58        inputs: &HashMap<String, String>,
59    ) -> Result<AgentOutput, AgentError>;
60
61    /// Returns input keys.
62    fn input_keys(&self) -> Vec<&str> {
63        vec!["input"]
64    }
65
66    /// Returns allowed tools list.
67    fn get_allowed_tools(&self) -> Option<Vec<&str>> {
68        None
69    }
70
71    /// Returns stopped response when max iterations reached.
72    fn return_stopped_response(&self, _intermediate_steps: &[AgentStep]) -> AgentFinish {
73        AgentFinish::new(
74            "Agent stopped due to iteration limit or time limit.".to_string(),
75            String::new(),
76        )
77    }
78}
79
80/// Agent executor.
81///
82/// Responsible for executing the agent's decision loop: Plan -> Act -> Observe.
83pub struct AgentExecutor {
84    /// Agent instance.
85    agent: Arc<dyn BaseAgent>,
86
87    /// Available tools.
88    tools: Vec<Arc<dyn BaseTool>>,
89
90    /// Max iterations.
91    max_iterations: usize,
92
93    /// Verbose output.
94    verbose: bool,
95
96    /// Memory (optional).
97    memory: Option<Arc<tokio::sync::Mutex<dyn BaseMemory>>>,
98
99    /// Callback manager (optional).
100    callbacks: Option<Arc<CallbackManager>>,
101}
102
103impl AgentExecutor {
104    /// Creates a new AgentExecutor.
105    pub fn new(agent: Arc<dyn BaseAgent>, tools: Vec<Arc<dyn BaseTool>>) -> Self {
106        Self {
107            agent,
108            tools,
109            max_iterations: 10,
110            verbose: false,
111            memory: None,
112            callbacks: None,
113        }
114    }
115
116    /// Sets max iterations.
117    pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
118        self.max_iterations = max_iterations;
119        self
120    }
121
122    /// Sets verbose output.
123    pub fn with_verbose(mut self, verbose: bool) -> Self {
124        self.verbose = verbose;
125        self
126    }
127
128    /// Sets memory.
129    pub fn with_memory(mut self, memory: Arc<tokio::sync::Mutex<dyn BaseMemory>>) -> Self {
130        self.memory = Some(memory);
131        self
132    }
133
134    /// Sets callback manager.
135    pub fn with_callbacks(mut self, callbacks: Arc<CallbackManager>) -> Self {
136        self.callbacks = Some(callbacks);
137        self
138    }
139
140    /// Executes the agent.
141    pub async fn invoke(&self, input: String) -> Result<String, AgentError> {
142        let mut root_run = RunTree::new(
143            "AgentExecutor",
144            RunType::Chain,
145            json!({"input": input.clone()}),
146        );
147
148        if let Some(ref callbacks) = self.callbacks {
149            for handler in callbacks.handlers() {
150                handler.on_chain_start(&root_run, &root_run.inputs).await;
151            }
152        }
153
154        let mut inputs = HashMap::new();
155        inputs.insert("input".to_string(), input.clone());
156
157        if let Some(memory) = &self.memory {
158            let memory_vars = memory
159                .lock()
160                .await
161                .load_memory_variables(&inputs)
162                .await
163                .map_err(|e| AgentError::Other(format!("Failed to load memory: {}", e)))?;
164
165            if let Some(history) = memory_vars.get("history") {
166                if let Some(history_str) = history.as_str() {
167                    inputs.insert("history".to_string(), history_str.to_string());
168                }
169            }
170        }
171
172        let intermediate_steps: Vec<AgentStep> = Vec::new();
173
174        let result = self
175            .run_agent_loop(inputs.clone(), intermediate_steps, &mut root_run)
176            .await;
177
178        if let Some(memory) = &self.memory {
179            if let Ok(ref output) = result {
180                let mut outputs = HashMap::new();
181                outputs.insert("output".to_string(), output.clone());
182
183                memory
184                    .lock()
185                    .await
186                    .save_context(&inputs, &outputs)
187                    .await
188                    .map_err(|e| AgentError::Other(format!("Failed to save memory: {}", e)))?;
189            }
190        }
191
192        match &result {
193            Ok(output) => {
194                root_run.end(json!({"output": output}));
195                if let Some(ref callbacks) = self.callbacks {
196                    if let Some(ref outputs) = root_run.outputs {
197                        for handler in callbacks.handlers() {
198                            handler.on_chain_end(&root_run, outputs).await;
199                        }
200                    }
201                }
202            }
203            Err(e) => {
204                root_run.end_with_error(e.to_string());
205                if let Some(ref callbacks) = self.callbacks {
206                    for handler in callbacks.handlers() {
207                        handler.on_chain_error(&root_run, &e.to_string()).await;
208                    }
209                }
210            }
211        }
212
213        result
214    }
215
216    /// Stream agent execution as a true async stream of events.
217    ///
218    /// Each step of the agent loop (tool calls, observations, final answer)
219    /// is emitted as an `AgentStreamEvent` as soon as it occurs.
220    ///
221    /// # Example
222    ///
223    /// ```rust,ignore
224    /// let mut stream = executor.stream("What is Rust?".to_string());
225    /// while let Some(event) = stream.next().await {
226    ///     match event {
227    ///         Ok(AgentStreamEvent::ToolStart { name, input }) => { /* show tool call */ }
228    ///         Ok(AgentStreamEvent::ToolEnd { name, output }) => { /* show result */ }
229    ///         Ok(AgentStreamEvent::FinalAnswer { content }) => { /* show answer */ }
230    ///         _ => {}
231    ///     }
232    /// }
233    /// ```
234    pub fn stream(&self, input: String) -> Pin<Box<dyn Stream<Item = Result<AgentStreamEvent, AgentError>> + Send>> {
235        let (tx, rx) = tokio::sync::mpsc::channel(32);
236
237        let agent = self.agent.clone();
238        let tools = self.tools.clone();
239        let max_iterations = self.max_iterations;
240        let verbose = self.verbose;
241
242        tokio::spawn(async move {
243            let mut intermediate_steps: Vec<AgentStep> = Vec::new();
244            let mut inputs = HashMap::new();
245            inputs.insert("input".to_string(), input);
246
247            for iteration in 0..max_iterations {
248                if verbose {
249                    log::info!("=== Stream Iteration {} ===", iteration + 1);
250                }
251
252                let output = match agent.plan(&intermediate_steps, &inputs).await {
253                    Ok(o) => o,
254                    Err(e) => {
255                        let _ = tx.send(Ok(AgentStreamEvent::Error { message: e.to_string() })).await;
256                        return;
257                    }
258                };
259
260                match output {
261                    AgentOutput::Finish(finish) => {
262                        let content = finish.output().unwrap_or("").to_string();
263                        let _ = tx.send(Ok(AgentStreamEvent::FinalAnswer { content })).await;
264                        return;
265                    }
266
267                    AgentOutput::Action(action) => {
268                        let tool_name = action.tool.clone();
269                        let tool_input_str = match &action.tool_input {
270                            super::types::ToolInput::String { value: s } => s.clone(),
271                            super::types::ToolInput::Object { value: v } => {
272                                serde_json::to_string(v).unwrap_or_default()
273                            }
274                        };
275
276                        let _ = tx.send(Ok(AgentStreamEvent::ToolStart {
277                            name: tool_name.clone(),
278                            input: tool_input_str.clone(),
279                        })).await;
280
281                        // Execute the tool
282                        let observation = match execute_tool_for_stream(&tools, &action).await {
283                            Ok(obs) => obs,
284                            Err(e) => {
285                                let _ = tx.send(Ok(AgentStreamEvent::Error { message: e.to_string() })).await;
286                                return;
287                            }
288                        };
289
290                        let _ = tx.send(Ok(AgentStreamEvent::ToolEnd {
291                            name: tool_name,
292                            output: observation.clone(),
293                        })).await;
294
295                        intermediate_steps.push(AgentStep::new(action, observation));
296                    }
297
298                    AgentOutput::Actions(actions) => {
299                        for action in &actions {
300                            let tool_name = action.tool.clone();
301                            let tool_input_str = match &action.tool_input {
302                                super::types::ToolInput::String { value: s } => s.clone(),
303                                super::types::ToolInput::Object { value: v } => {
304                                    serde_json::to_string(v).unwrap_or_default()
305                                }
306                            };
307
308                            let _ = tx.send(Ok(AgentStreamEvent::ToolStart {
309                                name: tool_name.clone(),
310                                input: tool_input_str,
311                            })).await;
312                        }
313
314                        let observations = execute_tools_parallel_for_stream(&tools, &actions).await;
315
316                        for (action, observation) in actions.into_iter().zip(observations.into_iter()) {
317                            let _ = tx.send(Ok(AgentStreamEvent::ToolEnd {
318                                name: action.tool.clone(),
319                                output: observation.clone(),
320                            })).await;
321
322                            intermediate_steps.push(AgentStep::new(action, observation));
323                        }
324                    }
325                }
326            }
327
328            // Max iterations reached
329            let finish = agent.return_stopped_response(&intermediate_steps);
330            let content = finish.output().unwrap_or("").to_string();
331            let _ = tx.send(Ok(AgentStreamEvent::FinalAnswer { content })).await;
332        });
333
334        Box::pin(tokio_stream::wrappers::ReceiverStream::new(rx))
335    }
336
337    /// Runs the agent loop.
338    async fn run_agent_loop(
339        &self,
340        inputs: HashMap<String, String>,
341        mut intermediate_steps: Vec<AgentStep>,
342        root_run: &mut RunTree,
343    ) -> Result<String, AgentError> {
344        for iteration in 0..self.max_iterations {
345            if self.verbose {
346                log::info!("=== Iteration {} ===", iteration + 1);
347            }
348
349            let output = self.agent.plan(&intermediate_steps, &inputs).await?;
350
351            match output {
352                AgentOutput::Finish(finish) => {
353                    if self.verbose {
354                        log::info!("Final answer: {:?}", finish.return_values);
355                    }
356                    return Ok(finish.output().unwrap_or("").to_string());
357                }
358
359                AgentOutput::Action(action) => {
360                    if self.verbose {
361                        log::info!("Action: {}({})", action.tool, action.tool_input);
362                    }
363
364                    let observation = self.execute_tool(&action, root_run).await?;
365
366                    if self.verbose {
367                        log::info!("Observation: {}", observation);
368                    }
369
370                    intermediate_steps.push(AgentStep::new(action, observation));
371                }
372
373                AgentOutput::Actions(actions) => {
374                    if self.verbose {
375                        log::info!("Parallel actions: {} count", actions.len());
376                        for action in &actions {
377                            log::info!("  - {}({})", action.tool, action.tool_input);
378                        }
379                    }
380
381                    let observations = self.execute_tools_parallel(&actions, root_run).await?;
382
383                    if self.verbose {
384                        for (i, obs) in observations.iter().enumerate() {
385                            log::info!("Observation {}: {}", i + 1, obs);
386                        }
387                    }
388
389                    for (action, observation) in actions.into_iter().zip(observations.into_iter()) {
390                        intermediate_steps.push(AgentStep::new(action, observation));
391                    }
392                }
393            }
394        }
395
396        if self.verbose {
397            log::info!("Max iterations reached: {}", self.max_iterations);
398        }
399
400        let finish = self.agent.return_stopped_response(&intermediate_steps);
401        Ok(finish.output().unwrap_or("").to_string())
402    }
403
404    /// Executes multiple tools in parallel.
405    ///
406    /// Collects successful results and reports failures as error observations
407    /// rather than discarding partial results when one tool fails.
408    async fn execute_tools_parallel(
409        &self,
410        actions: &[super::types::AgentAction],
411        root_run: &RunTree,
412    ) -> Result<Vec<String>, AgentError> {
413        use futures_util::future::join_all;
414
415        let futures: Vec<_> = actions
416            .iter()
417            .map(|action| self.execute_tool(action, root_run))
418            .collect();
419
420        let results = join_all(futures).await;
421        let mut observations = Vec::with_capacity(results.len());
422        for result in results {
423            match result {
424                Ok(output) => observations.push(output),
425                Err(e) => observations.push(format!("[Tool execution error: {}]", e)),
426            }
427        }
428        Ok(observations)
429    }
430
431    /// Executes a single tool.
432    async fn execute_tool(
433        &self,
434        action: &AgentAction,
435        root_run: &RunTree,
436    ) -> Result<String, AgentError> {
437        let tool = self
438            .tools
439            .iter()
440            .find(|t| t.name() == action.tool)
441            .ok_or_else(|| AgentError::ToolNotFound(action.tool.clone()))?;
442
443        let input_str = match &action.tool_input {
444            super::types::ToolInput::String { value: s } => s.clone(),
445            super::types::ToolInput::Object { value: v } => serde_json::to_string(v)
446                .map_err(|e| AgentError::Other(format!("Failed to serialize tool input: {}", e)))?,
447        };
448
449        let mut tool_run = root_run.create_child(
450            &action.tool,
451            RunType::Tool,
452            json!({"input": input_str.clone()}),
453        );
454
455        if let Some(ref callbacks) = self.callbacks {
456            for handler in callbacks.handlers() {
457                handler
458                    .on_tool_start(&tool_run, &action.tool, &input_str)
459                    .await;
460            }
461        }
462
463        let result = tool.run(input_str.clone()).await;
464
465        match result {
466            Ok(output) => {
467                tool_run.end(json!({"output": output.clone()}));
468                if let Some(ref callbacks) = self.callbacks {
469                    for handler in callbacks.handlers() {
470                        handler.on_tool_end(&tool_run, &output).await;
471                    }
472                }
473                Ok(output)
474            }
475            Err(e) => {
476                tool_run.end_with_error(e.to_string());
477                if let Some(ref callbacks) = self.callbacks {
478                    for handler in callbacks.handlers() {
479                        handler.on_tool_error(&tool_run, &e.to_string()).await;
480                    }
481                }
482                Err(AgentError::ToolExecutionError(e.to_string()))
483            }
484        }
485    }
486}
487
488impl std::fmt::Debug for AgentExecutor {
489    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
490        f.debug_struct("AgentExecutor")
491            .field("max_iterations", &self.max_iterations)
492            .field("verbose", &self.verbose)
493            .field("tools_count", &self.tools.len())
494            .field("has_memory", &self.memory.is_some())
495            .finish()
496    }
497}
498
499/// Helper: execute a single tool for streaming (no RunTree dependency).
500async fn execute_tool_for_stream(
501    tools: &[Arc<dyn BaseTool>],
502    action: &AgentAction,
503) -> Result<String, AgentError> {
504    let tool = tools
505        .iter()
506        .find(|t| t.name() == action.tool)
507        .ok_or_else(|| AgentError::ToolNotFound(action.tool.clone()))?;
508
509    let input_str = match &action.tool_input {
510        super::types::ToolInput::String { value: s } => s.clone(),
511        super::types::ToolInput::Object { value: v } => serde_json::to_string(v)
512            .map_err(|e| AgentError::Other(format!("Failed to serialize tool input: {}", e)))?,
513    };
514
515    tool.run(input_str)
516        .await
517        .map_err(|e| AgentError::ToolExecutionError(e.to_string()))
518}
519
520/// Helper: execute multiple tools in parallel for streaming.
521async fn execute_tools_parallel_for_stream(
522    tools: &[Arc<dyn BaseTool>],
523    actions: &[AgentAction],
524) -> Vec<String> {
525    use futures_util::future::join_all;
526
527    let futures: Vec<_> = actions
528        .iter()
529        .map(|action| execute_tool_for_stream(tools, action))
530        .collect();
531
532    let results = join_all(futures).await;
533    results
534        .into_iter()
535        .map(|result| result.unwrap_or_else(|e| format!("[Tool execution error: {}]", e)))
536        .collect()
537}
538
539#[cfg(test)]
540mod tests {
541    use super::*;
542    use lc_memory::ConversationBufferMemory;
543
544    /// Tests AgentExecutor with memory.
545    #[tokio::test]
546    async fn test_agent_executor_with_memory() {
547        // Create simple mock agent
548        struct TestAgent;
549
550        #[async_trait]
551        impl BaseAgent for TestAgent {
552            async fn plan(
553                &self,
554                _intermediate_steps: &[AgentStep],
555                inputs: &HashMap<String, String>,
556            ) -> Result<AgentOutput, AgentError> {
557                // If history exists, check if it contains previous info
558                if let Some(history) = inputs.get("history") {
559                    if history.contains("Zhang San") {
560                        return Ok(AgentOutput::Finish(AgentFinish::new(
561                            "Your name is Zhang San".to_string(),
562                            String::new(),
563                        )));
564                    }
565                }
566
567                // Otherwise return input content
568                let input = inputs.get("input").unwrap();
569                Ok(AgentOutput::Finish(AgentFinish::new(
570                    format!("Received: {}", input),
571                    String::new(),
572                )))
573            }
574        }
575
576        // Create memory
577        let memory = Arc::new(tokio::sync::Mutex::new(ConversationBufferMemory::new()));
578
579        // Create executor
580        let executor = AgentExecutor::new(Arc::new(TestAgent), vec![]).with_memory(memory);
581
582        // First conversation round
583        let result1 = executor
584            .invoke("My name is Zhang San".to_string())
585            .await
586            .unwrap();
587        println!("Round 1: {}", result1);
588
589        // Second conversation round - should remember the name
590        let result2 = executor
591            .invoke("What is my name?".to_string())
592            .await
593            .unwrap();
594        println!("Round 2: {}", result2);
595
596        assert!(result2.contains("Zhang San"));
597    }
598}