1use std::collections::HashMap;
14use std::sync::Arc;
15
16use lc_agents::AgentExecutor;
17use lc_chains::base::{BaseChain, ChainError, ChainResult};
18use serde_json::Value;
19
20pub struct AgentExecutorChain {
25 executor: Arc<AgentExecutor>,
26}
27
28impl AgentExecutorChain {
29 pub fn new(executor: Arc<AgentExecutor>) -> Self {
34 Self { executor }
35 }
36
37 pub fn inner(&self) -> &AgentExecutor {
39 &self.executor
40 }
41}
42
43#[async_trait::async_trait]
44impl BaseChain for AgentExecutorChain {
45 fn input_keys(&self) -> Vec<&str> {
46 vec!["input"]
47 }
48
49 fn output_keys(&self) -> Vec<&str> {
50 vec!["output"]
51 }
52
53 async fn invoke(&self, inputs: HashMap<String, Value>) -> Result<ChainResult, ChainError> {
54 let raw = inputs
55 .get("input")
56 .ok_or_else(|| ChainError::MissingInput("input".to_string()))?;
57 let input = raw
58 .as_str()
59 .ok_or_else(|| ChainError::InputError("input must be a string".to_string()))?
60 .to_string();
61
62 let output =
63 self.executor.invoke(input).await.map_err(|e| {
64 ChainError::ExecutionError(format!("Agent execution failed: {}", e))
65 })?;
66
67 let mut result = HashMap::new();
68 result.insert("output".to_string(), Value::String(output));
69 Ok(result)
70 }
71
72 fn name(&self) -> &str {
73 "agent-executor"
74 }
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80 use lc_agents::{AgentError, AgentFinish, AgentOutput, AgentStep, BaseAgent};
81 use serde_json::json;
82
83 struct EchoAgent;
85
86 #[async_trait::async_trait]
87 impl BaseAgent for EchoAgent {
88 async fn plan(
89 &self,
90 _intermediate_steps: &[AgentStep],
91 inputs: &HashMap<String, String>,
92 ) -> Result<AgentOutput, AgentError> {
93 let input = inputs.get("input").cloned().unwrap_or_default();
94 Ok(AgentOutput::Finish(AgentFinish::new(
95 format!("echo: {}", input),
96 String::new(),
97 )))
98 }
99 }
100
101 struct FailAgent;
103
104 #[async_trait::async_trait]
105 impl BaseAgent for FailAgent {
106 async fn plan(
107 &self,
108 _intermediate_steps: &[AgentStep],
109 _inputs: &HashMap<String, String>,
110 ) -> Result<AgentOutput, AgentError> {
111 Err(AgentError::Other("boom".to_string()))
112 }
113 }
114
115 fn echo_chain() -> AgentExecutorChain {
116 let executor = AgentExecutor::new(Arc::new(EchoAgent), Vec::new());
117 AgentExecutorChain::new(Arc::new(executor))
118 }
119
120 #[tokio::test]
121 async fn invokes_agent_and_returns_output() {
122 let chain = echo_chain();
123 let mut inputs = HashMap::new();
124 inputs.insert("input".to_string(), json!("hello"));
125 let result = chain.invoke(inputs).await.unwrap();
126 assert_eq!(result.get("output"), Some(&json!("echo: hello")));
127 }
128
129 #[tokio::test]
130 async fn missing_input_returns_missing_input_error() {
131 let chain = echo_chain();
132 let result = chain.invoke(HashMap::new()).await;
133 assert!(matches!(result, Err(ChainError::MissingInput(_))));
134 }
135
136 #[tokio::test]
137 async fn non_string_input_returns_input_error() {
138 let chain = echo_chain();
139 let mut inputs = HashMap::new();
140 inputs.insert("input".to_string(), json!(42));
141 let result = chain.invoke(inputs).await;
142 assert!(matches!(result, Err(ChainError::InputError(_))));
143 }
144
145 #[tokio::test]
146 async fn agent_error_maps_to_execution_error() {
147 let executor = AgentExecutor::new(Arc::new(FailAgent), Vec::new());
148 let chain = AgentExecutorChain::new(Arc::new(executor));
149 let mut inputs = HashMap::new();
150 inputs.insert("input".to_string(), json!("hi"));
151 let result = chain.invoke(inputs).await;
152 assert!(matches!(result, Err(ChainError::ExecutionError(_))));
153 }
154
155 #[test]
156 fn exposes_expected_keys_and_name() {
157 let chain = echo_chain();
158 assert_eq!(chain.input_keys(), vec!["input"]);
159 assert_eq!(chain.output_keys(), vec!["output"]);
160 assert_eq!(chain.name(), "agent-executor");
161 }
162}