Skip to main content

lc_agents/
builder.rs

1// src/agents/builder.rs
2//! AgentBuilder — 3 行代码创建 Agent
3//!
4//! 提供流畅的 Builder API,对标 rig 的 `client.agent(model).preamble(...).build()`。
5//!
6//! # Example
7//!
8//! ```ignore
9//! let agent = AgentBuilder::new()
10//!     .llm(OpenAIChat::new(OpenAIConfig::new("sk-...")))
11//!     .system("You are a helpful assistant.")
12//!     .tool(Calculator::new())
13//!     .build()?;
14//! ```
15
16use crate::{AgentError, BaseAgent, FunctionCallingAgent};
17use lc_core::language_models::BaseChatModel;
18use lc_core::tools::BaseTool;
19use lc_providers::ProviderError;
20use std::sync::Arc;
21
22/// Agent Builder — 流畅 API 创建 FunctionCallingAgent
23///
24/// 使用 Builder 模式,让用户只需 3 行代码就能创建并运行 Agent。
25///
26/// # 基本用法
27///
28/// ```ignore
29/// let agent = AgentBuilder::new()
30///     .llm(OpenAIChat::new(OpenAIConfig::new("sk-...")))
31///     .system("You are a helpful assistant.")
32///     .tool(Calculator::new())
33///     .build()?;
34/// ```
35///
36/// # 使用 Arc<dyn BaseChatModel>
37///
38/// ```ignore
39/// let llm = wrap_chat_model(OpenAIChat::new(config));
40/// let agent = AgentBuilder::new()
41///     .llm_from_arc(llm)
42///     .system("You are a helpful assistant.")
43///     .build()?;
44/// ```
45pub struct AgentBuilder {
46    llm: Option<Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>>,
47    system_prompt: Option<String>,
48    tools: Vec<Arc<dyn BaseTool>>,
49    max_iterations: usize,
50}
51
52impl AgentBuilder {
53    /// 创建新的 AgentBuilder
54    pub fn new() -> Self {
55        Self {
56            llm: None,
57            system_prompt: None,
58            tools: Vec::new(),
59            max_iterations: 10,
60        }
61    }
62
63    /// 设置 LLM(任何实现了 `BaseChatModel` 的类型)
64    ///
65    /// 自动包装为 `Arc<dyn BaseChatModel<Error = ProviderError>>`。
66    pub fn llm<L>(mut self, llm: L) -> Self
67    where
68        L: BaseChatModel + Send + Sync + 'static,
69        L::Error: Into<ProviderError>,
70    {
71        self.llm = Some(lc_providers::wrap_chat_model(llm));
72        self
73    }
74
75    /// 设置 LLM(从已包装的 `Arc<dyn BaseChatModel>`)
76    ///
77    /// 适用于已通过 `wrap_chat_model()` 或 `LLMClient` 创建的 LLM 实例。
78    pub fn llm_from_arc(
79        mut self,
80        llm: Arc<dyn BaseChatModel<Error = ProviderError> + Send + Sync>,
81    ) -> Self {
82        self.llm = Some(llm);
83        self
84    }
85
86    /// 设置系统提示词
87    pub fn system(mut self, prompt: impl Into<String>) -> Self {
88        self.system_prompt = Some(prompt.into());
89        self
90    }
91
92    /// 添加单个工具
93    pub fn tool<T: BaseTool + 'static>(mut self, tool: T) -> Self {
94        self.tools.push(Arc::new(tool));
95        self
96    }
97
98    /// 添加多个工具
99    pub fn tools(mut self, tools: Vec<Arc<dyn BaseTool>>) -> Self {
100        self.tools.extend(tools);
101        self
102    }
103
104    /// 设置最大迭代次数
105    pub fn max_iterations(mut self, n: usize) -> Self {
106        self.max_iterations = n;
107        self
108    }
109
110    /// 构建 FunctionCallingAgent
111    ///
112    /// # Errors
113    ///
114    /// 如果没有设置 LLM,返回 `AgentError::Other`。
115    pub fn build(self) -> Result<FunctionCallingAgent, AgentError> {
116        let llm = self.llm.ok_or_else(|| {
117            AgentError::Other("AgentBuilder: LLM is required. Call .llm() first.".into())
118        })?;
119
120        Ok(FunctionCallingAgent::from_arc(
121            llm,
122            self.tools,
123            self.system_prompt,
124        ))
125    }
126
127    /// 构建并包装为 `Arc<dyn BaseAgent>`,可直接传给 `AgentExecutor`
128    ///
129    /// # Errors
130    ///
131    /// 如果没有设置 LLM,返回 `AgentError::Other`。
132    pub fn build_as_agent(self) -> Result<Arc<dyn BaseAgent>, AgentError> {
133        let agent = self.build()?;
134        Ok(Arc::new(agent) as Arc<dyn BaseAgent>)
135    }
136
137    /// 获取最大迭代次数
138    pub fn get_max_iterations(&self) -> usize {
139        self.max_iterations
140    }
141}
142
143impl Default for AgentBuilder {
144    fn default() -> Self {
145        Self::new()
146    }
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use lc_providers::{OpenAIChat, OpenAIConfig};
153    use lc_tools::Calculator;
154
155    #[test]
156    fn test_builder_with_openai() {
157        let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
158        let agent = AgentBuilder::new()
159            .llm(OpenAIChat::new(config))
160            .system("You are a test assistant.")
161            .tool(Calculator::new())
162            .build()
163            .unwrap();
164
165        assert_eq!(agent.tools_count(), 1);
166        assert_eq!(agent.system_prompt(), Some("You are a test assistant."));
167    }
168
169    #[test]
170    fn test_builder_missing_llm() {
171        let result = AgentBuilder::new().system("test").build();
172
173        assert!(result.is_err());
174        let err = result.unwrap_err();
175        assert!(err.to_string().contains("LLM is required"));
176    }
177
178    #[test]
179    fn test_builder_multiple_tools() {
180        let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
181        let agent = AgentBuilder::new()
182            .llm(OpenAIChat::new(config))
183            .tool(Calculator::new())
184            .tool(Calculator::new())
185            .build()
186            .unwrap();
187
188        assert_eq!(agent.tools_count(), 2);
189    }
190
191    #[test]
192    fn test_builder_tools_vec() {
193        let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
194        let tools: Vec<Arc<dyn BaseTool>> =
195            vec![Arc::new(Calculator::new()), Arc::new(Calculator::new())];
196        let agent = AgentBuilder::new()
197            .llm(OpenAIChat::new(config))
198            .tools(tools)
199            .build()
200            .unwrap();
201
202        assert_eq!(agent.tools_count(), 2);
203    }
204
205    #[test]
206    fn test_builder_build_as_agent() {
207        let config = OpenAIConfig::new("test_key").with_base_url("http://localhost:8080/v1");
208        let agent = AgentBuilder::new()
209            .llm(OpenAIChat::new(config))
210            .system("test")
211            .build_as_agent()
212            .unwrap();
213
214        let allowed = agent.get_allowed_tools();
215        assert!(allowed.is_some());
216    }
217
218    #[test]
219    fn test_builder_max_iterations() {
220        let builder = AgentBuilder::new().max_iterations(5);
221        assert_eq!(builder.get_max_iterations(), 5);
222    }
223
224    #[test]
225    fn test_builder_default() {
226        let builder = AgentBuilder::default();
227        assert_eq!(builder.get_max_iterations(), 10);
228        assert!(builder.llm.is_none());
229    }
230}