1use std::borrow::Borrow;
2use std::collections::BTreeMap;
3use std::sync::Arc;
4
5use serde::de::DeserializeOwned;
6
7use crate::guardrails::{InputGuardrail, OutputGuardrail};
8use crate::handoffs::Handoff;
9use crate::model::ModelRef;
10use crate::model_settings::ModelSettings;
11use crate::runtime::RuntimeHook;
12use crate::tools::common::trim_portable_whitespace;
13use crate::tools::{AgentToolBuilder, BackgroundAgentTaskBuilder};
14use crate::tools::{Tool, ToolPolicy};
15use crate::types::{Metadata, NoToolPolicy, SubAgentConfig};
16
17pub type InstructionProvider =
18 Arc<dyn Fn(&crate::context::RunContext, &Agent) -> String + Send + Sync>;
19pub type OutputValidator = Arc<dyn Fn(&str) -> Result<(), String> + Send + Sync>;
20
21#[derive(Clone)]
22pub struct Agent {
23 name: String,
24 instructions: String,
25 instruction_provider: Option<InstructionProvider>,
26 model: Option<ModelRef>,
27 model_settings: ModelSettings,
28 tools: Vec<Arc<dyn Tool>>,
29 handoffs: Vec<Handoff>,
30 input_guardrails: Vec<Arc<dyn InputGuardrail>>,
31 output_guardrails: Vec<Arc<dyn OutputGuardrail>>,
32 output_type_name: Option<&'static str>,
33 output_validator: Option<OutputValidator>,
34 hooks: Vec<Arc<dyn RuntimeHook>>,
35 max_cycles: Option<u32>,
36 no_tool_policy: Option<NoToolPolicy>,
37 tool_use_behavior: ToolUseBehavior,
38 tool_policy: ToolPolicy,
39 sub_agents: BTreeMap<String, SubAgentConfig>,
40 metadata: Metadata,
41}
42
43impl Agent {
44 pub fn builder(name: impl Into<String>) -> AgentBuilder {
45 AgentBuilder {
46 agent: Self {
47 name: name.into(),
48 instructions: String::new(),
49 instruction_provider: None,
50 model: None,
51 model_settings: ModelSettings::default(),
52 tools: Vec::new(),
53 handoffs: Vec::new(),
54 input_guardrails: Vec::new(),
55 output_guardrails: Vec::new(),
56 output_type_name: None,
57 output_validator: None,
58 hooks: Vec::new(),
59 max_cycles: None,
60 no_tool_policy: None,
61 tool_use_behavior: ToolUseBehavior::default(),
62 tool_policy: ToolPolicy::default(),
63 sub_agents: BTreeMap::new(),
64 metadata: Metadata::new(),
65 },
66 sub_agent_error: None,
67 }
68 }
69
70 pub fn name(&self) -> &str {
71 &self.name
72 }
73
74 pub fn instructions(&self) -> &str {
75 &self.instructions
76 }
77
78 pub(crate) fn has_dynamic_instructions(&self) -> bool {
79 self.instruction_provider.is_some()
80 }
81
82 pub fn resolve_instructions(&self, context: &crate::context::RunContext) -> String {
83 self.instruction_provider
84 .as_ref()
85 .map(|provider| provider(context, self))
86 .unwrap_or_else(|| self.instructions.clone())
87 }
88
89 pub fn model(&self) -> Option<&ModelRef> {
90 self.model.as_ref()
91 }
92
93 pub fn model_settings(&self) -> &ModelSettings {
94 &self.model_settings
95 }
96
97 pub fn tools(&self) -> &[Arc<dyn Tool>] {
98 &self.tools
99 }
100
101 pub fn handoffs(&self) -> &[Handoff] {
102 &self.handoffs
103 }
104
105 pub(crate) fn input_guardrails(&self) -> &[Arc<dyn InputGuardrail>] {
106 &self.input_guardrails
107 }
108
109 pub(crate) fn output_guardrails(&self) -> &[Arc<dyn OutputGuardrail>] {
110 &self.output_guardrails
111 }
112
113 pub fn output_type_name(&self) -> Option<&'static str> {
114 self.output_type_name
115 }
116
117 pub fn validate_output(&self, output: &str) -> Result<(), String> {
118 match self.output_validator.as_ref() {
119 Some(validator) => validator(output),
120 None => Ok(()),
121 }
122 }
123
124 pub fn hooks(&self) -> &[Arc<dyn RuntimeHook>] {
125 &self.hooks
126 }
127
128 pub fn max_cycles(&self) -> Option<u32> {
129 self.max_cycles
130 }
131
132 pub fn no_tool_policy(&self) -> Option<NoToolPolicy> {
133 self.no_tool_policy
134 }
135
136 pub fn tool_use_behavior(&self) -> &ToolUseBehavior {
137 &self.tool_use_behavior
138 }
139
140 pub fn tool_policy(&self) -> &ToolPolicy {
141 &self.tool_policy
142 }
143
144 pub fn sub_agents(&self) -> &BTreeMap<String, SubAgentConfig> {
145 &self.sub_agents
146 }
147
148 pub fn metadata(&self) -> &Metadata {
149 &self.metadata
150 }
151
152 pub fn as_tool(&self) -> AgentToolBuilder {
153 AgentToolBuilder::new(self.clone())
154 }
155
156 pub fn as_background_task(&self) -> BackgroundAgentTaskBuilder {
157 BackgroundAgentTaskBuilder::new(self.clone())
158 }
159}
160
161pub struct AgentBuilder {
162 agent: Agent,
163 sub_agent_error: Option<String>,
164}
165
166impl AgentBuilder {
167 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
168 self.agent.instructions = instructions.into();
169 self.agent.instruction_provider = None;
170 self
171 }
172
173 pub fn dynamic_instructions(
174 mut self,
175 provider: impl Fn(&crate::context::RunContext, &Agent) -> String + Send + Sync + 'static,
176 ) -> Self {
177 self.agent.instructions.clear();
178 self.agent.instruction_provider = Some(Arc::new(provider));
179 self
180 }
181
182 pub fn model(mut self, model: ModelRef) -> Self {
183 self.agent.model = Some(model);
184 self
185 }
186
187 pub fn model_settings(mut self, settings: ModelSettings) -> Self {
188 self.agent.model_settings = settings;
189 self
190 }
191
192 pub fn tool(mut self, tool: impl Tool + 'static) -> Self {
193 self.agent.tools.push(Arc::new(tool));
194 self
195 }
196
197 pub fn tool_arc(mut self, tool: Arc<dyn Tool>) -> Self {
198 self.agent.tools.push(tool);
199 self
200 }
201
202 pub fn handoff(mut self, handoff: impl Into<Handoff>) -> Self {
203 self.agent.handoffs.push(handoff.into());
204 self
205 }
206
207 pub fn input_guardrail(mut self, guardrail: Arc<dyn InputGuardrail>) -> Self {
208 self.agent.input_guardrails.push(guardrail);
209 self
210 }
211
212 pub fn output_guardrail(mut self, guardrail: Arc<dyn OutputGuardrail>) -> Self {
213 self.agent.output_guardrails.push(guardrail);
214 self
215 }
216
217 pub fn output_type<T>(mut self) -> Self
218 where
219 T: DeserializeOwned + 'static,
220 {
221 self.agent.output_type_name = Some(std::any::type_name::<T>());
222 self.agent.output_validator = Some(Arc::new(|output| {
223 serde_json::from_str::<T>(output)
224 .map(|_| ())
225 .map_err(|error| error.to_string())
226 }));
227 self
228 }
229
230 pub fn output_validator(
231 mut self,
232 name: &'static str,
233 validator: impl Fn(&str) -> Result<(), String> + Send + Sync + 'static,
234 ) -> Self {
235 self.agent.output_type_name = Some(name);
236 self.agent.output_validator = Some(Arc::new(validator));
237 self
238 }
239
240 pub fn hook(mut self, hook: Arc<dyn RuntimeHook>) -> Self {
241 self.agent.hooks.push(hook);
242 self
243 }
244
245 pub fn max_cycles(mut self, max_cycles: u32) -> Self {
246 self.agent.max_cycles = Some(max_cycles);
247 self
248 }
249
250 pub fn no_tool_policy(mut self, policy: NoToolPolicy) -> Self {
251 self.agent.no_tool_policy = Some(policy);
252 self
253 }
254
255 pub fn tool_use_behavior(mut self, behavior: ToolUseBehavior) -> Self {
256 self.agent.tool_use_behavior = behavior;
257 self
258 }
259
260 pub fn tool_policy(mut self, tool_policy: ToolPolicy) -> Self {
261 self.agent.tool_policy = tool_policy;
262 self
263 }
264
265 pub fn sub_agent(mut self, id: impl AsRef<str>, config: impl Borrow<SubAgentConfig>) -> Self {
266 self.insert_sub_agent(id.as_ref(), config.borrow());
267 self
268 }
269
270 pub fn sub_agents<I, K, C>(mut self, sub_agents: I) -> Self
271 where
272 I: IntoIterator<Item = (K, C)>,
273 K: AsRef<str>,
274 C: Borrow<SubAgentConfig>,
275 {
276 for (id, config) in sub_agents {
277 self.insert_sub_agent(id.as_ref(), config.borrow());
278 }
279 self
280 }
281
282 pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
283 self.agent.metadata.insert(key.into(), value);
284 self
285 }
286
287 pub fn build(self) -> Result<Agent, String> {
288 if self.agent.name.trim().is_empty() {
289 return Err("agent name cannot be empty".to_string());
290 }
291 if self.agent.instructions.trim().is_empty() && self.agent.instruction_provider.is_none() {
292 return Err("agent instructions cannot be empty".to_string());
293 }
294 if let Some(error) = self.sub_agent_error {
295 return Err(error);
296 }
297 Ok(self.agent)
298 }
299
300 fn insert_sub_agent(&mut self, id: &str, config: &SubAgentConfig) {
301 let normalized_id = trim_portable_whitespace(id);
302 if normalized_id.is_empty() {
303 self.sub_agent_error
304 .get_or_insert_with(|| "sub-agent id cannot be empty".to_string());
305 return;
306 }
307 if self.agent.sub_agents.contains_key(normalized_id) {
308 self.sub_agent_error.get_or_insert_with(|| {
309 format!("duplicate sub-agent id after normalization: {normalized_id}")
310 });
311 return;
312 }
313 self.agent
314 .sub_agents
315 .insert(normalized_id.to_string(), config.clone());
316 }
317}
318
319#[derive(Clone, Default)]
320pub enum ToolUseBehavior {
321 #[default]
322 RunLlmAgain,
323 StopOnFirstTool,
324 StopAtToolNames(Vec<String>),
325}