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::agent::ToolHandle;
17use agents_core::llm::LanguageModel;
18use agents_core::persistence::Checkpointer;
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<Arc<dyn ToolHandle>>,
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}
36
37impl ConfigurableAgentBuilder {
38    pub fn new(instructions: impl Into<String>) -> Self {
39        Self {
40            instructions: instructions.into(),
41            planner: None,
42            tools: Vec::new(),
43            subagents: Vec::new(),
44            summarization: None,
45            tool_interrupts: HashMap::new(),
46            builtin_tools: None,
47            auto_general_purpose: true,
48            enable_prompt_caching: false,
49            checkpointer: None,
50        }
51    }
52
53    /// Set the language model for the agent (mirrors Python's `model` parameter)
54    pub fn with_model(mut self, model: Arc<dyn LanguageModel>) -> Self {
55        let planner: Arc<dyn PlannerHandle> = Arc::new(LlmBackedPlanner::new(model));
56        self.planner = Some(planner);
57        self
58    }
59
60    /// Low-level planner API (for advanced use cases)
61    pub fn with_planner(mut self, planner: Arc<dyn PlannerHandle>) -> Self {
62        self.planner = Some(planner);
63        self
64    }
65
66    /// Convenience method for OpenAI models (equivalent to model=OpenAiChatModel)
67    pub fn with_openai_chat(self, config: OpenAiConfig) -> anyhow::Result<Self> {
68        let model = Arc::new(OpenAiChatModel::new(config)?);
69        Ok(self.with_model(model))
70    }
71
72    /// Convenience method for Anthropic models (equivalent to model=AnthropicMessagesModel)  
73    pub fn with_anthropic_messages(self, config: AnthropicConfig) -> anyhow::Result<Self> {
74        let model = Arc::new(AnthropicMessagesModel::new(config)?);
75        Ok(self.with_model(model))
76    }
77
78    /// Convenience method for Gemini models (equivalent to model=GeminiChatModel)
79    pub fn with_gemini_chat(self, config: GeminiConfig) -> anyhow::Result<Self> {
80        let model = Arc::new(GeminiChatModel::new(config)?);
81        Ok(self.with_model(model))
82    }
83
84    pub fn with_tool(mut self, tool: Arc<dyn ToolHandle>) -> Self {
85        self.tools.push(tool);
86        self
87    }
88
89    pub fn with_tools<I>(mut self, tools: I) -> Self
90    where
91        I: IntoIterator<Item = Arc<dyn ToolHandle>>,
92    {
93        self.tools.extend(tools);
94        self
95    }
96
97    pub fn with_subagent_config<I>(mut self, cfgs: I) -> Self
98    where
99        I: IntoIterator<Item = SubAgentConfig>,
100    {
101        self.subagents.extend(cfgs);
102        self
103    }
104
105    /// Convenience method: automatically create subagents from a list of tools.
106    /// Each tool becomes a specialized subagent with that single tool.
107    pub fn with_subagent_tools<I>(mut self, tools: I) -> Self
108    where
109        I: IntoIterator<Item = Arc<dyn ToolHandle>>,
110    {
111        for tool in tools {
112            let tool_name = tool.name();
113            let subagent_config = SubAgentConfig {
114                name: format!("{}-agent", tool_name),
115                description: format!("Specialized agent for {} operations", tool_name),
116                instructions: format!(
117                    "You are a specialized agent. Use the {} tool to complete tasks efficiently.",
118                    tool_name
119                ),
120                tools: Some(vec![tool]),
121                planner: None, // Will inherit from parent
122            };
123            self.subagents.push(subagent_config);
124        }
125        self
126    }
127
128    pub fn with_summarization(mut self, config: SummarizationConfig) -> Self {
129        self.summarization = Some(config);
130        self
131    }
132
133    pub fn with_tool_interrupt(mut self, tool_name: impl Into<String>, policy: HitlPolicy) -> Self {
134        self.tool_interrupts.insert(tool_name.into(), policy);
135        self
136    }
137
138    pub fn with_builtin_tools<I, S>(mut self, names: I) -> Self
139    where
140        I: IntoIterator<Item = S>,
141        S: Into<String>,
142    {
143        self.builtin_tools = Some(names.into_iter().map(|s| s.into()).collect());
144        self
145    }
146
147    pub fn with_auto_general_purpose(mut self, enabled: bool) -> Self {
148        self.auto_general_purpose = enabled;
149        self
150    }
151
152    pub fn with_prompt_caching(mut self, enabled: bool) -> Self {
153        self.enable_prompt_caching = enabled;
154        self
155    }
156
157    pub fn with_checkpointer(mut self, checkpointer: Arc<dyn Checkpointer>) -> Self {
158        self.checkpointer = Some(checkpointer);
159        self
160    }
161
162    pub fn build(self) -> anyhow::Result<DeepAgent> {
163        self.finalize(create_deep_agent_from_config)
164    }
165
166    /// Build an agent using the async constructor alias. This mirrors the Python
167    /// async_create_deep_agent entry point, while reusing the same runtime internals.
168    pub fn build_async(self) -> anyhow::Result<DeepAgent> {
169        self.finalize(create_async_deep_agent_from_config)
170    }
171
172    fn finalize(self, ctor: fn(DeepAgentConfig) -> DeepAgent) -> anyhow::Result<DeepAgent> {
173        let Self {
174            instructions,
175            planner,
176            tools,
177            subagents,
178            summarization,
179            tool_interrupts,
180            builtin_tools,
181            auto_general_purpose,
182            enable_prompt_caching,
183            checkpointer,
184        } = self;
185
186        let planner = planner
187            .ok_or_else(|| anyhow::anyhow!("model must be set (use with_model or with_*_chat)"))?;
188
189        let mut cfg = DeepAgentConfig::new(instructions, planner)
190            .with_auto_general_purpose(auto_general_purpose)
191            .with_prompt_caching(enable_prompt_caching);
192
193        if let Some(ckpt) = checkpointer {
194            cfg = cfg.with_checkpointer(ckpt);
195        }
196        if let Some(sum) = summarization {
197            cfg = cfg.with_summarization(sum);
198        }
199        if let Some(selected) = builtin_tools {
200            cfg = cfg.with_builtin_tools(selected);
201        }
202        for (name, policy) in tool_interrupts {
203            cfg = cfg.with_tool_interrupt(name, policy);
204        }
205        for tool in tools {
206            cfg = cfg.with_tool(tool);
207        }
208        for sub_cfg in subagents {
209            cfg = cfg.with_subagent_config(sub_cfg);
210        }
211
212        Ok(ctor(cfg))
213    }
214}