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 async_trait::async_trait;
6use lc_callbacks::{CallbackManager, RunTree, RunType};
7use lc_core::tools::BaseTool;
8use lc_memory::BaseMemory;
9use serde_json::json;
10use std::collections::HashMap;
11use std::sync::Arc;
12
13/// Agent error types.
14#[derive(Debug, thiserror::Error)]
15pub enum AgentError {
16    /// Output parsing error.
17    #[error("Output parsing error: {0}")]
18    OutputParsingError(String),
19
20    /// Tool not found.
21    #[error("Tool not found: {0}")]
22    ToolNotFound(String),
23
24    /// Tool execution error.
25    #[error("Tool execution error: {0}")]
26    ToolExecutionError(String),
27
28    /// Max iterations reached.
29    #[error("Max iterations reached")]
30    MaxIterationsReached,
31
32    /// Other error.
33    #[error("Agent error: {0}")]
34    Other(String),
35}
36
37/// Base Agent trait.
38///
39/// Defines the core interface for agents. Agent is responsible for planning,
40/// not execution. Execution is handled by AgentExecutor.
41#[async_trait]
42pub trait BaseAgent: Send + Sync {
43    /// Plans the next action.
44    ///
45    /// # Arguments
46    /// * `intermediate_steps` - History of executed steps.
47    /// * `inputs` - User input.
48    ///
49    /// # Returns
50    /// * `AgentOutput::Action` - Action to execute.
51    /// * `AgentOutput::Finish` - Final answer.
52    async fn plan(
53        &self,
54        intermediate_steps: &[AgentStep],
55        inputs: &HashMap<String, String>,
56    ) -> Result<AgentOutput, AgentError>;
57
58    /// Returns input keys.
59    fn input_keys(&self) -> Vec<&str> {
60        vec!["input"]
61    }
62
63    /// Returns allowed tools list.
64    fn get_allowed_tools(&self) -> Option<Vec<&str>> {
65        None
66    }
67
68    /// Returns stopped response when max iterations reached.
69    fn return_stopped_response(&self, _intermediate_steps: &[AgentStep]) -> AgentFinish {
70        AgentFinish::new(
71            "Agent stopped due to iteration limit or time limit.".to_string(),
72            String::new(),
73        )
74    }
75}
76
77/// Agent executor.
78///
79/// Responsible for executing the agent's decision loop: Plan -> Act -> Observe.
80pub struct AgentExecutor {
81    /// Agent instance.
82    agent: Arc<dyn BaseAgent>,
83
84    /// Available tools.
85    tools: Vec<Arc<dyn BaseTool>>,
86
87    /// Max iterations.
88    max_iterations: usize,
89
90    /// Verbose output.
91    verbose: bool,
92
93    /// Memory (optional).
94    memory: Option<Arc<tokio::sync::Mutex<dyn BaseMemory>>>,
95
96    /// Callback manager (optional).
97    callbacks: Option<Arc<CallbackManager>>,
98}
99
100impl AgentExecutor {
101    /// Creates a new AgentExecutor.
102    pub fn new(agent: Arc<dyn BaseAgent>, tools: Vec<Arc<dyn BaseTool>>) -> Self {
103        Self {
104            agent,
105            tools,
106            max_iterations: 10,
107            verbose: false,
108            memory: None,
109            callbacks: None,
110        }
111    }
112
113    /// Sets max iterations.
114    pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
115        self.max_iterations = max_iterations;
116        self
117    }
118
119    /// Sets verbose output.
120    pub fn with_verbose(mut self, verbose: bool) -> Self {
121        self.verbose = verbose;
122        self
123    }
124
125    /// Sets memory.
126    pub fn with_memory(mut self, memory: Arc<tokio::sync::Mutex<dyn BaseMemory>>) -> Self {
127        self.memory = Some(memory);
128        self
129    }
130
131    /// Sets callback manager.
132    pub fn with_callbacks(mut self, callbacks: Arc<CallbackManager>) -> Self {
133        self.callbacks = Some(callbacks);
134        self
135    }
136
137    /// Executes the agent.
138    pub async fn invoke(&self, input: String) -> Result<String, AgentError> {
139        let mut root_run = RunTree::new(
140            "AgentExecutor",
141            RunType::Chain,
142            json!({"input": input.clone()}),
143        );
144
145        if let Some(ref callbacks) = self.callbacks {
146            for handler in callbacks.handlers() {
147                handler.on_chain_start(&root_run, &root_run.inputs).await;
148            }
149        }
150
151        let mut inputs = HashMap::new();
152        inputs.insert("input".to_string(), input.clone());
153
154        if let Some(memory) = &self.memory {
155            let memory_vars = memory
156                .lock()
157                .await
158                .load_memory_variables(&inputs)
159                .await
160                .map_err(|e| AgentError::Other(format!("Failed to load memory: {}", e)))?;
161
162            if let Some(history) = memory_vars.get("history") {
163                if let Some(history_str) = history.as_str() {
164                    inputs.insert("history".to_string(), history_str.to_string());
165                }
166            }
167        }
168
169        let intermediate_steps: Vec<AgentStep> = Vec::new();
170
171        let result = self
172            .run_agent_loop(inputs.clone(), intermediate_steps, &mut root_run)
173            .await;
174
175        if let Some(memory) = &self.memory {
176            if let Ok(ref output) = result {
177                let mut outputs = HashMap::new();
178                outputs.insert("output".to_string(), output.clone());
179
180                memory
181                    .lock()
182                    .await
183                    .save_context(&inputs, &outputs)
184                    .await
185                    .map_err(|e| AgentError::Other(format!("Failed to save memory: {}", e)))?;
186            }
187        }
188
189        match &result {
190            Ok(output) => {
191                root_run.end(json!({"output": output}));
192                if let Some(ref callbacks) = self.callbacks {
193                    if let Some(ref outputs) = root_run.outputs {
194                        for handler in callbacks.handlers() {
195                            handler.on_chain_end(&root_run, outputs).await;
196                        }
197                    }
198                }
199            }
200            Err(e) => {
201                root_run.end_with_error(e.to_string());
202                if let Some(ref callbacks) = self.callbacks {
203                    for handler in callbacks.handlers() {
204                        handler.on_chain_error(&root_run, &e.to_string()).await;
205                    }
206                }
207            }
208        }
209
210        result
211    }
212
213    /// Runs the agent loop.
214    async fn run_agent_loop(
215        &self,
216        inputs: HashMap<String, String>,
217        mut intermediate_steps: Vec<AgentStep>,
218        root_run: &mut RunTree,
219    ) -> Result<String, AgentError> {
220        for iteration in 0..self.max_iterations {
221            if self.verbose {
222                log::info!("=== Iteration {} ===", iteration + 1);
223            }
224
225            let output = self.agent.plan(&intermediate_steps, &inputs).await?;
226
227            match output {
228                AgentOutput::Finish(finish) => {
229                    if self.verbose {
230                        log::info!("Final answer: {:?}", finish.return_values);
231                    }
232                    return Ok(finish.output().unwrap_or("").to_string());
233                }
234
235                AgentOutput::Action(action) => {
236                    if self.verbose {
237                        log::info!("Action: {}({})", action.tool, action.tool_input);
238                    }
239
240                    let observation = self.execute_tool(&action, root_run).await?;
241
242                    if self.verbose {
243                        log::info!("Observation: {}", observation);
244                    }
245
246                    intermediate_steps.push(AgentStep::new(action, observation));
247                }
248
249                AgentOutput::Actions(actions) => {
250                    if self.verbose {
251                        log::info!("Parallel actions: {} count", actions.len());
252                        for action in &actions {
253                            log::info!("  - {}({})", action.tool, action.tool_input);
254                        }
255                    }
256
257                    let observations = self.execute_tools_parallel(&actions, root_run).await?;
258
259                    if self.verbose {
260                        for (i, obs) in observations.iter().enumerate() {
261                            log::info!("Observation {}: {}", i + 1, obs);
262                        }
263                    }
264
265                    for (action, observation) in actions.into_iter().zip(observations.into_iter()) {
266                        intermediate_steps.push(AgentStep::new(action, observation));
267                    }
268                }
269            }
270        }
271
272        if self.verbose {
273            log::info!("Max iterations reached: {}", self.max_iterations);
274        }
275
276        let finish = self.agent.return_stopped_response(&intermediate_steps);
277        Ok(finish.output().unwrap_or("").to_string())
278    }
279
280    /// Executes multiple tools in parallel.
281    ///
282    /// Collects successful results and reports failures as error observations
283    /// rather than discarding partial results when one tool fails.
284    async fn execute_tools_parallel(
285        &self,
286        actions: &[super::types::AgentAction],
287        root_run: &RunTree,
288    ) -> Result<Vec<String>, AgentError> {
289        use futures_util::future::join_all;
290
291        let futures: Vec<_> = actions
292            .iter()
293            .map(|action| self.execute_tool(action, root_run))
294            .collect();
295
296        let results = join_all(futures).await;
297        let mut observations = Vec::with_capacity(results.len());
298        for result in results {
299            match result {
300                Ok(output) => observations.push(output),
301                Err(e) => observations.push(format!("[Tool execution error: {}]", e)),
302            }
303        }
304        Ok(observations)
305    }
306
307    /// Executes a single tool.
308    async fn execute_tool(
309        &self,
310        action: &AgentAction,
311        root_run: &RunTree,
312    ) -> Result<String, AgentError> {
313        let tool = self
314            .tools
315            .iter()
316            .find(|t| t.name() == action.tool)
317            .ok_or_else(|| AgentError::ToolNotFound(action.tool.clone()))?;
318
319        let input_str = match &action.tool_input {
320            super::types::ToolInput::String { value: s } => s.clone(),
321            super::types::ToolInput::Object { value: v } => serde_json::to_string(v)
322                .map_err(|e| AgentError::Other(format!("Failed to serialize tool input: {}", e)))?,
323        };
324
325        let mut tool_run = root_run.create_child(
326            &action.tool,
327            RunType::Tool,
328            json!({"input": input_str.clone()}),
329        );
330
331        if let Some(ref callbacks) = self.callbacks {
332            for handler in callbacks.handlers() {
333                handler
334                    .on_tool_start(&tool_run, &action.tool, &input_str)
335                    .await;
336            }
337        }
338
339        let result = tool.run(input_str.clone()).await;
340
341        match result {
342            Ok(output) => {
343                tool_run.end(json!({"output": output.clone()}));
344                if let Some(ref callbacks) = self.callbacks {
345                    for handler in callbacks.handlers() {
346                        handler.on_tool_end(&tool_run, &output).await;
347                    }
348                }
349                Ok(output)
350            }
351            Err(e) => {
352                tool_run.end_with_error(e.to_string());
353                if let Some(ref callbacks) = self.callbacks {
354                    for handler in callbacks.handlers() {
355                        handler.on_tool_error(&tool_run, &e.to_string()).await;
356                    }
357                }
358                Err(AgentError::ToolExecutionError(e.to_string()))
359            }
360        }
361    }
362}
363
364impl std::fmt::Debug for AgentExecutor {
365    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366        f.debug_struct("AgentExecutor")
367            .field("max_iterations", &self.max_iterations)
368            .field("verbose", &self.verbose)
369            .field("tools_count", &self.tools.len())
370            .field("has_memory", &self.memory.is_some())
371            .finish()
372    }
373}
374
375#[cfg(test)]
376mod tests {
377    use super::*;
378    use lc_memory::ConversationBufferMemory;
379
380    /// Tests AgentExecutor with memory.
381    #[tokio::test]
382    async fn test_agent_executor_with_memory() {
383        // Create simple mock agent
384        struct TestAgent;
385
386        #[async_trait]
387        impl BaseAgent for TestAgent {
388            async fn plan(
389                &self,
390                _intermediate_steps: &[AgentStep],
391                inputs: &HashMap<String, String>,
392            ) -> Result<AgentOutput, AgentError> {
393                // If history exists, check if it contains previous info
394                if let Some(history) = inputs.get("history") {
395                    if history.contains("Zhang San") {
396                        return Ok(AgentOutput::Finish(AgentFinish::new(
397                            "Your name is Zhang San".to_string(),
398                            String::new(),
399                        )));
400                    }
401                }
402
403                // Otherwise return input content
404                let input = inputs.get("input").unwrap();
405                Ok(AgentOutput::Finish(AgentFinish::new(
406                    format!("Received: {}", input),
407                    String::new(),
408                )))
409            }
410        }
411
412        // Create memory
413        let memory = Arc::new(tokio::sync::Mutex::new(ConversationBufferMemory::new()));
414
415        // Create executor
416        let executor = AgentExecutor::new(Arc::new(TestAgent), vec![]).with_memory(memory);
417
418        // First conversation round
419        let result1 = executor
420            .invoke("My name is Zhang San".to_string())
421            .await
422            .unwrap();
423        println!("Round 1: {}", result1);
424
425        // Second conversation round - should remember the name
426        let result2 = executor
427            .invoke("What is my name?".to_string())
428            .await
429            .unwrap();
430        println!("Round 2: {}", result2);
431
432        assert!(result2.contains("Zhang San"));
433    }
434}