use crate::agents::{AgentError, BaseAgent, FunctionCallingAgent};
use crate::core::language_models::BaseChatModel;
use crate::core::tools::BaseTool;
use crate::error::Error;
use std::sync::Arc;
pub struct AgentBuilder {
llm: Option<Arc<dyn BaseChatModel<Error = Error> + Send + Sync>>,
system_prompt: Option<String>,
tools: Vec<Arc<dyn BaseTool>>,
max_iterations: usize,
}
impl AgentBuilder {
pub fn new() -> Self {
Self {
llm: None,
system_prompt: None,
tools: Vec::new(),
max_iterations: 10,
}
}
pub fn llm<L>(mut self, llm: L) -> Self
where
L: BaseChatModel + Send + Sync + 'static,
L::Error: Into<Error>,
{
self.llm = Some(crate::core::language_models::wrap_chat_model(llm));
self
}
pub fn llm_from_arc(mut self, llm: Arc<dyn BaseChatModel<Error = Error> + Send + Sync>) -> Self {
self.llm = Some(llm);
self
}
pub fn system(mut self, prompt: impl Into<String>) -> Self {
self.system_prompt = Some(prompt.into());
self
}
pub fn tool<T: BaseTool + 'static>(mut self, tool: T) -> Self {
self.tools.push(Arc::new(tool));
self
}
pub fn tools(mut self, tools: Vec<Arc<dyn BaseTool>>) -> Self {
self.tools.extend(tools);
self
}
pub fn max_iterations(mut self, n: usize) -> Self {
self.max_iterations = n;
self
}
pub fn build(self) -> Result<FunctionCallingAgent, AgentError> {
let llm = self
.llm
.ok_or_else(|| AgentError::Other("AgentBuilder: LLM is required. Call .llm() first.".into()))?;
Ok(FunctionCallingAgent::from_arc(llm, self.tools, self.system_prompt))
}
pub fn build_as_agent(self) -> Result<Arc<dyn BaseAgent>, AgentError> {
let agent = self.build()?;
Ok(Arc::new(agent) as Arc<dyn BaseAgent>)
}
pub fn get_max_iterations(&self) -> usize {
self.max_iterations
}
}
impl Default for AgentBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::language_models::{OpenAIChat, OpenAIConfig};
use crate::tools::Calculator;
#[test]
fn test_builder_with_openai() {
let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
let agent = AgentBuilder::new()
.llm(OpenAIChat::new(config))
.system("You are a test assistant.")
.tool(Calculator::new())
.build()
.unwrap();
assert_eq!(agent.tools_count(), 1);
assert_eq!(agent.system_prompt(), Some("You are a test assistant."));
}
#[test]
fn test_builder_missing_llm() {
let result = AgentBuilder::new()
.system("test")
.build();
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.to_string().contains("LLM is required"));
}
#[test]
fn test_builder_multiple_tools() {
let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
let agent = AgentBuilder::new()
.llm(OpenAIChat::new(config))
.tool(Calculator::new())
.tool(Calculator::new())
.build()
.unwrap();
assert_eq!(agent.tools_count(), 2);
}
#[test]
fn test_builder_tools_vec() {
let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
let tools: Vec<Arc<dyn BaseTool>> = vec![Arc::new(Calculator::new()), Arc::new(Calculator::new())];
let agent = AgentBuilder::new()
.llm(OpenAIChat::new(config))
.tools(tools)
.build()
.unwrap();
assert_eq!(agent.tools_count(), 2);
}
#[test]
fn test_builder_build_as_agent() {
let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
let agent = AgentBuilder::new()
.llm(OpenAIChat::new(config))
.system("test")
.build_as_agent()
.unwrap();
let allowed = agent.get_allowed_tools();
assert!(allowed.is_some());
}
#[test]
fn test_builder_max_iterations() {
let builder = AgentBuilder::new().max_iterations(5);
assert_eq!(builder.get_max_iterations(), 5);
}
#[test]
fn test_builder_default() {
let builder = AgentBuilder::default();
assert_eq!(builder.get_max_iterations(), 10);
assert!(builder.llm.is_none());
}
}