1use crate::{AgentError, BaseAgent, FunctionCallingAgent};
18use lc_core::language_models::BaseChatModel;
19use lc_core::tools::BaseTool;
20use lc_providers::ProviderError;
21use std::sync::Arc;
22
23pub struct AgentBuilder {
48 llm: Option<Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>>,
49 system_prompt: Option<String>,
50 tools: Vec<Arc<dyn BaseTool>>,
51 max_iterations: usize,
52}
53
54impl AgentBuilder {
55 pub fn new() -> Self {
57 Self {
58 llm: None,
59 system_prompt: None,
60 tools: Vec::new(),
61 max_iterations: 10,
62 }
63 }
64
65 pub fn llm<L>(mut self, llm: L) -> Self
69 where
70 L: BaseChatModel + Send + Sync + 'static,
71 L::Error: Into<ProviderError>,
72 {
73 self.llm = Some(lc_providers::wrap_chat_model(llm));
74 self
75 }
76
77 pub fn llm_from_arc(
81 mut self,
82 llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
83 ) -> Self {
84 self.llm = Some(llm);
85 self
86 }
87
88 pub fn system(mut self, prompt: impl Into<String>) -> Self {
90 self.system_prompt = Some(prompt.into());
91 self
92 }
93
94 pub fn tool<T: BaseTool + 'static>(mut self, tool: T) -> Self {
96 self.tools.push(Arc::new(tool));
97 self
98 }
99
100 pub fn tools(mut self, tools: Vec<Arc<dyn BaseTool>>) -> Self {
102 self.tools.extend(tools);
103 self
104 }
105
106 pub fn max_iterations(mut self, n: usize) -> Self {
108 const MIN_MAX_ITERATIONS: usize = 1;
109 const MAX_MAX_ITERATIONS: usize = 100;
110 self.max_iterations = n.clamp(MIN_MAX_ITERATIONS, MAX_MAX_ITERATIONS);
111 if n > MAX_MAX_ITERATIONS {
112 log::warn!("max_iterations {} clamped to {}", n, MAX_MAX_ITERATIONS);
113 }
114 self
115 }
116
117 pub fn build(self) -> Result<FunctionCallingAgent, AgentError> {
123 let llm = self.llm.ok_or_else(|| {
124 AgentError::Other("AgentBuilder: LLM is required. Call .llm() first.".into())
125 })?;
126
127 Ok(FunctionCallingAgent::from_arc(
128 llm,
129 self.tools,
130 self.system_prompt,
131 ))
132 }
133
134 pub fn build_as_agent(self) -> Result<Arc<dyn BaseAgent>, AgentError> {
140 let agent = self.build()?;
141 Ok(Arc::new(agent) as Arc<dyn BaseAgent>)
142 }
143
144 pub fn get_max_iterations(&self) -> usize {
146 self.max_iterations
147 }
148}
149
150impl Default for AgentBuilder {
151 fn default() -> Self {
152 Self::new()
153 }
154}
155
156#[cfg(test)]
157mod tests {
158 use super::*;
159 use lc_providers::{OpenAIChat, OpenAIConfig};
160 use lc_tools::Calculator;
161
162 #[test]
163 fn test_builder_with_openai() {
164 let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
165 let agent = AgentBuilder::new()
166 .llm(OpenAIChat::new(config))
167 .system("You are a test assistant.")
168 .tool(Calculator::new())
169 .build()
170 .unwrap();
171
172 assert_eq!(agent.tools_count(), 1);
173 assert_eq!(agent.system_prompt(), Some("You are a test assistant."));
174 }
175
176 #[test]
177 fn test_builder_missing_llm() {
178 let result = AgentBuilder::new().system("test").build();
179
180 assert!(result.is_err());
181 let err = result.unwrap_err();
182 assert!(err.to_string().contains("LLM is required"));
183 }
184
185 #[test]
186 fn test_builder_multiple_tools() {
187 let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
188 let agent = AgentBuilder::new()
189 .llm(OpenAIChat::new(config))
190 .tool(Calculator::new())
191 .tool(Calculator::new())
192 .build()
193 .unwrap();
194
195 assert_eq!(agent.tools_count(), 2);
196 }
197
198 #[test]
199 fn test_builder_tools_vec() {
200 let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
201 let tools: Vec<Arc<dyn BaseTool>> =
202 vec![Arc::new(Calculator::new()), Arc::new(Calculator::new())];
203 let agent = AgentBuilder::new()
204 .llm(OpenAIChat::new(config))
205 .tools(tools)
206 .build()
207 .unwrap();
208
209 assert_eq!(agent.tools_count(), 2);
210 }
211
212 #[test]
213 fn test_builder_build_as_agent() {
214 let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
215 let agent = AgentBuilder::new()
216 .llm(OpenAIChat::new(config))
217 .system("test")
218 .build_as_agent()
219 .unwrap();
220
221 let allowed = agent.get_allowed_tools();
222 assert!(allowed.is_some());
223 }
224
225 #[test]
226 fn test_builder_max_iterations() {
227 let builder = AgentBuilder::new().max_iterations(5);
228 assert_eq!(builder.get_max_iterations(), 5);
229 }
230
231 #[test]
232 fn test_builder_max_iterations_clamped_to_min() {
233 let builder = AgentBuilder::new().max_iterations(0);
234 assert_eq!(builder.get_max_iterations(), 1);
235 }
236
237 #[test]
238 fn test_builder_max_iterations_clamped_to_max() {
239 let builder = AgentBuilder::new().max_iterations(1_000_000_000);
240 assert_eq!(builder.get_max_iterations(), 100);
241 }
242
243 #[test]
244 fn test_builder_default() {
245 let builder = AgentBuilder::default();
246 assert_eq!(builder.get_max_iterations(), 10);
247 assert!(builder.llm.is_none());
248 }
249}