agents_runtime/agent/
builder.rs

1//! Fluent builder API for constructing Deep Agents
2//!
3//! This module provides the ConfigurableAgentBuilder that offers a fluent interface
4//! for building Deep Agents, mirroring the Python SDK's ergonomic construction patterns.
5
6use super::api::{create_async_deep_agent_from_config, create_deep_agent_from_config};
7use super::config::{DeepAgentConfig, SubAgentConfig, SummarizationConfig};
8use super::runtime::DeepAgent;
9use crate::middleware::HitlPolicy;
10use crate::planner::LlmBackedPlanner;
11use crate::providers::{
12    AnthropicConfig, AnthropicMessagesModel, GeminiChatModel, GeminiConfig, OpenAiChatModel,
13    OpenAiConfig,
14};
15use agents_core::agent::PlannerHandle;
16use agents_core::llm::LanguageModel;
17use agents_core::persistence::Checkpointer;
18use agents_core::tools::ToolBox;
19use std::collections::{HashMap, HashSet};
20use std::sync::Arc;
21
22/// Builder API to assemble a DeepAgent in a single fluent flow, mirroring the Python
23/// `create_configurable_agent` experience. Prefer this for ergonomic construction.
24pub struct ConfigurableAgentBuilder {
25    instructions: String,
26    planner: Option<Arc<dyn PlannerHandle>>,
27    tools: Vec<ToolBox>,
28    subagents: Vec<SubAgentConfig>,
29    summarization: Option<SummarizationConfig>,
30    tool_interrupts: HashMap<String, HitlPolicy>,
31    builtin_tools: Option<HashSet<String>>,
32    auto_general_purpose: bool,
33    enable_prompt_caching: bool,
34    checkpointer: Option<Arc<dyn Checkpointer>>,
35    event_dispatcher: Option<Arc<agents_core::events::EventDispatcher>>,
36}
37
38impl ConfigurableAgentBuilder {
39    pub fn new(instructions: impl Into<String>) -> Self {
40        Self {
41            instructions: instructions.into(),
42            planner: None,
43            tools: Vec::new(),
44            subagents: Vec::new(),
45            summarization: None,
46            tool_interrupts: HashMap::new(),
47            builtin_tools: None,
48            auto_general_purpose: true,
49            enable_prompt_caching: false,
50            checkpointer: None,
51            event_dispatcher: None,
52        }
53    }
54
55    /// Set the language model for the agent (mirrors Python's `model` parameter)
56    pub fn with_model(mut self, model: Arc<dyn LanguageModel>) -> Self {
57        let planner: Arc<dyn PlannerHandle> = Arc::new(LlmBackedPlanner::new(model));
58        self.planner = Some(planner);
59        self
60    }
61
62    /// Low-level planner API (for advanced use cases)
63    pub fn with_planner(mut self, planner: Arc<dyn PlannerHandle>) -> Self {
64        self.planner = Some(planner);
65        self
66    }
67
68    /// Convenience method for OpenAI models (equivalent to model=OpenAiChatModel)
69    pub fn with_openai_chat(self, config: OpenAiConfig) -> anyhow::Result<Self> {
70        let model = Arc::new(OpenAiChatModel::new(config)?);
71        Ok(self.with_model(model))
72    }
73
74    /// Convenience method for Anthropic models (equivalent to model=AnthropicMessagesModel)  
75    pub fn with_anthropic_messages(self, config: AnthropicConfig) -> anyhow::Result<Self> {
76        let model = Arc::new(AnthropicMessagesModel::new(config)?);
77        Ok(self.with_model(model))
78    }
79
80    /// Convenience method for Gemini models (equivalent to model=GeminiChatModel)
81    pub fn with_gemini_chat(self, config: GeminiConfig) -> anyhow::Result<Self> {
82        let model = Arc::new(GeminiChatModel::new(config)?);
83        Ok(self.with_model(model))
84    }
85
86    /// Add a tool to the agent
87    pub fn with_tool(mut self, tool: ToolBox) -> Self {
88        self.tools.push(tool);
89        self
90    }
91
92    /// Add multiple tools
93    pub fn with_tools<I>(mut self, tools: I) -> Self
94    where
95        I: IntoIterator<Item = ToolBox>,
96    {
97        self.tools.extend(tools);
98        self
99    }
100
101    pub fn with_subagent_config<I>(mut self, cfgs: I) -> Self
102    where
103        I: IntoIterator<Item = SubAgentConfig>,
104    {
105        self.subagents.extend(cfgs);
106        self
107    }
108
109    /// Convenience method: automatically create subagents from a list of tools.
110    /// Each tool becomes a specialized subagent with that single tool.
111    pub fn with_subagent_tools<I>(mut self, tools: I) -> Self
112    where
113        I: IntoIterator<Item = ToolBox>,
114    {
115        for tool in tools {
116            let tool_name = tool.schema().name.clone();
117            let subagent_config = SubAgentConfig::new(
118                format!("{}-agent", tool_name),
119                format!("Specialized agent for {} operations", tool_name),
120                format!(
121                    "You are a specialized agent. Use the {} tool to complete tasks efficiently.",
122                    tool_name
123                ),
124            )
125            .with_tools(vec![tool]);
126            self.subagents.push(subagent_config);
127        }
128        self
129    }
130
131    pub fn with_summarization(mut self, config: SummarizationConfig) -> Self {
132        self.summarization = Some(config);
133        self
134    }
135
136    pub fn with_tool_interrupt(mut self, tool_name: impl Into<String>, policy: HitlPolicy) -> Self {
137        self.tool_interrupts.insert(tool_name.into(), policy);
138        self
139    }
140
141    pub fn with_builtin_tools<I, S>(mut self, names: I) -> Self
142    where
143        I: IntoIterator<Item = S>,
144        S: Into<String>,
145    {
146        self.builtin_tools = Some(names.into_iter().map(|s| s.into()).collect());
147        self
148    }
149
150    pub fn with_auto_general_purpose(mut self, enabled: bool) -> Self {
151        self.auto_general_purpose = enabled;
152        self
153    }
154
155    pub fn with_prompt_caching(mut self, enabled: bool) -> Self {
156        self.enable_prompt_caching = enabled;
157        self
158    }
159
160    pub fn with_checkpointer(mut self, checkpointer: Arc<dyn Checkpointer>) -> Self {
161        self.checkpointer = Some(checkpointer);
162        self
163    }
164
165    /// Add a single event broadcaster to the agent
166    ///
167    /// Example:
168    /// ```ignore
169    /// builder.with_event_broadcaster(console_broadcaster)
170    /// ```
171    pub fn with_event_broadcaster(
172        mut self,
173        broadcaster: Arc<dyn agents_core::events::EventBroadcaster>,
174    ) -> Self {
175        // Create dispatcher if it doesn't exist
176        if self.event_dispatcher.is_none() {
177            self.event_dispatcher = Some(Arc::new(agents_core::events::EventDispatcher::new()));
178        }
179
180        // Add broadcaster to the dispatcher (uses interior mutability)
181        if let Some(dispatcher) = &self.event_dispatcher {
182            dispatcher.add_broadcaster(broadcaster);
183        }
184
185        self
186    }
187
188    /// Add multiple event broadcasters at once (cleaner API)
189    ///
190    /// Example:
191    /// ```ignore
192    /// builder.with_event_broadcasters(vec![
193    ///     console_broadcaster,
194    ///     whatsapp_broadcaster,
195    ///     dynamodb_broadcaster,
196    /// ])
197    /// ```
198    pub fn with_event_broadcasters(
199        mut self,
200        broadcasters: Vec<Arc<dyn agents_core::events::EventBroadcaster>>,
201    ) -> Self {
202        // Create dispatcher if it doesn't exist
203        if self.event_dispatcher.is_none() {
204            self.event_dispatcher = Some(Arc::new(agents_core::events::EventDispatcher::new()));
205        }
206
207        // Add all broadcasters
208        if let Some(dispatcher) = &self.event_dispatcher {
209            for broadcaster in broadcasters {
210                dispatcher.add_broadcaster(broadcaster);
211            }
212        }
213
214        self
215    }
216
217    /// Set the event dispatcher directly (replaces any existing dispatcher)
218    pub fn with_event_dispatcher(
219        mut self,
220        dispatcher: Arc<agents_core::events::EventDispatcher>,
221    ) -> Self {
222        self.event_dispatcher = Some(dispatcher);
223        self
224    }
225
226    pub fn build(self) -> anyhow::Result<DeepAgent> {
227        self.finalize(create_deep_agent_from_config)
228    }
229
230    /// Build an agent using the async constructor alias. This mirrors the Python
231    /// async_create_deep_agent entry point, while reusing the same runtime internals.
232    pub fn build_async(self) -> anyhow::Result<DeepAgent> {
233        self.finalize(create_async_deep_agent_from_config)
234    }
235
236    fn finalize(self, ctor: fn(DeepAgentConfig) -> DeepAgent) -> anyhow::Result<DeepAgent> {
237        let Self {
238            instructions,
239            planner,
240            tools,
241            subagents,
242            summarization,
243            tool_interrupts,
244            builtin_tools,
245            auto_general_purpose,
246            enable_prompt_caching,
247            checkpointer,
248            event_dispatcher,
249        } = self;
250
251        let planner = planner
252            .ok_or_else(|| anyhow::anyhow!("model must be set (use with_model or with_*_chat)"))?;
253
254        let mut cfg = DeepAgentConfig::new(instructions, planner)
255            .with_auto_general_purpose(auto_general_purpose)
256            .with_prompt_caching(enable_prompt_caching);
257
258        if let Some(ckpt) = checkpointer {
259            cfg = cfg.with_checkpointer(ckpt);
260        }
261        if let Some(dispatcher) = event_dispatcher {
262            cfg = cfg.with_event_dispatcher(dispatcher);
263        }
264        if let Some(sum) = summarization {
265            cfg = cfg.with_summarization(sum);
266        }
267        if let Some(selected) = builtin_tools {
268            cfg = cfg.with_builtin_tools(selected);
269        }
270        for (name, policy) in tool_interrupts {
271            cfg = cfg.with_tool_interrupt(name, policy);
272        }
273        for tool in tools {
274            cfg = cfg.with_tool(tool);
275        }
276        for sub_cfg in subagents {
277            cfg = cfg.with_subagent_config(sub_cfg);
278        }
279
280        Ok(ctor(cfg))
281    }
282}