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::output_validation::{
12 HostOutputValidator, OutputRepair, OutputRepairRequest, OutputValidationContext,
13 OutputValidationResult,
14};
15use crate::prompt::PromptBundle;
16use crate::runtime::RuntimeHook;
17use crate::tools::common::trim_portable_whitespace;
18use crate::tools::{AgentToolBuilder, BackgroundAgentTaskBuilder};
19use crate::tools::{Tool, ToolPolicy};
20use crate::types::{Metadata, NoToolPolicy, SubAgentConfig};
21
22pub type InstructionProvider =
23 Arc<dyn Fn(&crate::context::RunContext, &Agent) -> String + Send + Sync>;
24pub type PromptBundleProvider =
25 Arc<dyn Fn(&crate::context::RunContext, &Agent) -> PromptBundle + Send + Sync>;
26pub type OutputValidator = Arc<dyn Fn(&str) -> Result<(), String> + Send + Sync>;
27
28#[derive(Clone)]
29pub struct Agent {
30 name: String,
31 instructions: String,
32 prompt_bundle: Option<PromptBundle>,
33 instruction_provider: Option<InstructionProvider>,
34 prompt_bundle_provider: Option<PromptBundleProvider>,
35 model: Option<ModelRef>,
36 model_settings: ModelSettings,
37 tools: Vec<Arc<dyn Tool>>,
38 handoffs: Vec<Handoff>,
39 input_guardrails: Vec<Arc<dyn InputGuardrail>>,
40 output_guardrails: Vec<Arc<dyn OutputGuardrail>>,
41 output_type_name: Option<&'static str>,
42 output_validator: Option<OutputValidator>,
43 output_validation_enabled: bool,
44 host_output_validator: Option<HostOutputValidator>,
45 output_repair: Option<OutputRepair>,
46 output_validation_max_repairs: u8,
47 output_repair_model: Option<ModelRef>,
48 output_repair_model_settings: Option<ModelSettings>,
49 hooks: Vec<Arc<dyn RuntimeHook>>,
50 max_cycles: Option<u32>,
51 no_tool_policy: Option<NoToolPolicy>,
52 tool_use_behavior: ToolUseBehavior,
53 tool_policy: ToolPolicy,
54 sub_agents: BTreeMap<String, SubAgentConfig>,
55 metadata: Metadata,
56}
57
58impl Agent {
59 pub fn builder(name: impl Into<String>) -> AgentBuilder {
60 AgentBuilder {
61 agent: Self {
62 name: name.into(),
63 instructions: String::new(),
64 prompt_bundle: None,
65 instruction_provider: None,
66 prompt_bundle_provider: None,
67 model: None,
68 model_settings: ModelSettings::default(),
69 tools: Vec::new(),
70 handoffs: Vec::new(),
71 input_guardrails: Vec::new(),
72 output_guardrails: Vec::new(),
73 output_type_name: None,
74 output_validator: None,
75 output_validation_enabled: false,
76 host_output_validator: None,
77 output_repair: None,
78 output_validation_max_repairs: 1,
79 output_repair_model: None,
80 output_repair_model_settings: None,
81 hooks: Vec::new(),
82 max_cycles: None,
83 no_tool_policy: None,
84 tool_use_behavior: ToolUseBehavior::default(),
85 tool_policy: ToolPolicy::default(),
86 sub_agents: BTreeMap::new(),
87 metadata: Metadata::new(),
88 },
89 sub_agent_error: None,
90 output_validation_error: None,
91 }
92 }
93
94 pub fn name(&self) -> &str {
95 &self.name
96 }
97
98 pub fn instructions(&self) -> &str {
99 &self.instructions
100 }
101
102 pub(crate) fn has_dynamic_instructions(&self) -> bool {
103 self.instruction_provider.is_some() || self.prompt_bundle_provider.is_some()
104 }
105
106 pub fn resolve_instructions(&self, context: &crate::context::RunContext) -> String {
107 self.resolve_prompt_bundle(context)
108 .map(|bundle| bundle.flatten())
109 .unwrap_or_default()
110 }
111
112 pub fn resolve_prompt_bundle(
113 &self,
114 context: &crate::context::RunContext,
115 ) -> Result<PromptBundle, String> {
116 if let Some(provider) = self.prompt_bundle_provider.as_ref() {
117 let bundle = provider(context, self);
118 bundle.validate()?;
119 return Ok(bundle);
120 }
121 if let Some(provider) = self.instruction_provider.as_ref() {
122 return PromptBundle::from_instruction_text(provider(context, self));
123 }
124 if let Some(bundle) = self.prompt_bundle.as_ref() {
125 bundle.validate()?;
126 return Ok(bundle.clone());
127 }
128 PromptBundle::from_instruction_text(self.instructions.clone())
129 }
130
131 pub fn model(&self) -> Option<&ModelRef> {
132 self.model.as_ref()
133 }
134
135 pub fn model_settings(&self) -> &ModelSettings {
136 &self.model_settings
137 }
138
139 pub fn tools(&self) -> &[Arc<dyn Tool>] {
140 &self.tools
141 }
142
143 pub fn handoffs(&self) -> &[Handoff] {
144 &self.handoffs
145 }
146
147 pub(crate) fn input_guardrails(&self) -> &[Arc<dyn InputGuardrail>] {
148 &self.input_guardrails
149 }
150
151 pub(crate) fn output_guardrails(&self) -> &[Arc<dyn OutputGuardrail>] {
152 &self.output_guardrails
153 }
154
155 pub fn output_type_name(&self) -> Option<&'static str> {
156 self.output_type_name
157 }
158
159 pub fn validate_output(&self, output: &str) -> Result<(), String> {
160 match self.output_validator.as_ref() {
161 Some(validator) => validator(output),
162 None => Ok(()),
163 }
164 }
165
166 pub fn output_validation_enabled(&self) -> bool {
167 self.output_validation_enabled
168 }
169
170 pub fn host_output_validator(&self) -> Option<&HostOutputValidator> {
171 self.host_output_validator.as_ref()
172 }
173
174 pub fn output_repair(&self) -> Option<&OutputRepair> {
175 self.output_repair.as_ref()
176 }
177
178 pub fn output_validation_max_repairs(&self) -> u8 {
179 self.output_validation_max_repairs
180 }
181
182 pub fn output_repair_model(&self) -> Option<&ModelRef> {
183 self.output_repair_model.as_ref()
184 }
185
186 pub fn output_repair_model_settings(&self) -> Option<&ModelSettings> {
187 self.output_repair_model_settings.as_ref()
188 }
189
190 pub fn hooks(&self) -> &[Arc<dyn RuntimeHook>] {
191 &self.hooks
192 }
193
194 pub fn max_cycles(&self) -> Option<u32> {
195 self.max_cycles
196 }
197
198 pub fn no_tool_policy(&self) -> Option<NoToolPolicy> {
199 self.no_tool_policy
200 }
201
202 pub fn tool_use_behavior(&self) -> &ToolUseBehavior {
203 &self.tool_use_behavior
204 }
205
206 pub fn tool_policy(&self) -> &ToolPolicy {
207 &self.tool_policy
208 }
209
210 pub fn sub_agents(&self) -> &BTreeMap<String, SubAgentConfig> {
211 &self.sub_agents
212 }
213
214 pub fn metadata(&self) -> &Metadata {
215 &self.metadata
216 }
217
218 pub fn as_tool(&self) -> AgentToolBuilder {
219 AgentToolBuilder::new(self.clone())
220 }
221
222 pub fn as_background_task(&self) -> BackgroundAgentTaskBuilder {
223 BackgroundAgentTaskBuilder::new(self.clone())
224 }
225}
226
227pub struct AgentBuilder {
228 agent: Agent,
229 sub_agent_error: Option<String>,
230 output_validation_error: Option<String>,
231}
232
233impl AgentBuilder {
234 pub fn instructions(mut self, instructions: impl Into<String>) -> Self {
235 self.agent.instructions = instructions.into();
236 self.agent.prompt_bundle = None;
237 self.agent.instruction_provider = None;
238 self.agent.prompt_bundle_provider = None;
239 self
240 }
241
242 pub fn prompt_bundle(mut self, prompt_bundle: PromptBundle) -> Self {
243 self.agent.instructions = prompt_bundle.flatten();
244 self.agent.prompt_bundle = Some(prompt_bundle);
245 self.agent.instruction_provider = None;
246 self.agent.prompt_bundle_provider = None;
247 self
248 }
249
250 pub fn dynamic_instructions(
251 mut self,
252 provider: impl Fn(&crate::context::RunContext, &Agent) -> String + Send + Sync + 'static,
253 ) -> Self {
254 self.agent.instructions.clear();
255 self.agent.prompt_bundle = None;
256 self.agent.instruction_provider = Some(Arc::new(provider));
257 self.agent.prompt_bundle_provider = None;
258 self
259 }
260
261 pub fn dynamic_prompt_bundle(
262 mut self,
263 provider: impl Fn(&crate::context::RunContext, &Agent) -> PromptBundle + Send + Sync + 'static,
264 ) -> Self {
265 self.agent.instructions.clear();
266 self.agent.prompt_bundle = None;
267 self.agent.instruction_provider = None;
268 self.agent.prompt_bundle_provider = Some(Arc::new(provider));
269 self
270 }
271
272 pub fn model(mut self, model: ModelRef) -> Self {
273 self.agent.model = Some(model);
274 self
275 }
276
277 pub fn model_settings(mut self, settings: ModelSettings) -> Self {
278 self.agent.model_settings = settings;
279 self
280 }
281
282 pub fn tool(mut self, tool: impl Tool + 'static) -> Self {
283 self.agent.tools.push(Arc::new(tool));
284 self
285 }
286
287 pub fn tool_arc(mut self, tool: Arc<dyn Tool>) -> Self {
288 self.agent.tools.push(tool);
289 self
290 }
291
292 pub fn handoff(mut self, handoff: impl Into<Handoff>) -> Self {
293 self.agent.handoffs.push(handoff.into());
294 self
295 }
296
297 pub fn input_guardrail(mut self, guardrail: Arc<dyn InputGuardrail>) -> Self {
298 self.agent.input_guardrails.push(guardrail);
299 self
300 }
301
302 pub fn output_guardrail(mut self, guardrail: Arc<dyn OutputGuardrail>) -> Self {
303 self.agent.output_guardrails.push(guardrail);
304 self
305 }
306
307 pub fn output_type<T>(mut self) -> Self
308 where
309 T: DeserializeOwned + 'static,
310 {
311 self.agent.output_type_name = Some(std::any::type_name::<T>());
312 self.agent.output_validator = Some(Arc::new(|output| {
313 serde_json::from_str::<T>(output)
314 .map(|_| ())
315 .map_err(|error| error.to_string())
316 }));
317 self
318 }
319
320 pub fn output_validator(
321 mut self,
322 name: &'static str,
323 validator: impl Fn(&str) -> Result<(), String> + Send + Sync + 'static,
324 ) -> Self {
325 self.agent.output_type_name = Some(name);
326 self.agent.output_validator = Some(Arc::new(validator));
327 self
328 }
329
330 pub fn output_validation_enabled(mut self, enabled: bool) -> Self {
331 self.agent.output_validation_enabled = enabled;
332 self
333 }
334
335 pub fn host_output_validator(
336 mut self,
337 validator: impl Fn(&str, &OutputValidationContext) -> OutputValidationResult
338 + Send
339 + Sync
340 + 'static,
341 ) -> Self {
342 self.agent.host_output_validator = Some(Arc::new(validator));
343 self
344 }
345
346 pub fn output_repair(
347 mut self,
348 repair: impl Fn(&OutputRepairRequest) -> Result<String, String> + Send + Sync + 'static,
349 ) -> Self {
350 self.agent.output_repair = Some(Arc::new(repair));
351 self
352 }
353
354 pub fn output_validation_max_repairs(mut self, max_repairs: u8) -> Self {
355 if max_repairs > 1 {
356 self.output_validation_error =
357 Some("output_validation_max_repairs must be 0 or 1".to_string());
358 } else {
359 self.agent.output_validation_max_repairs = max_repairs;
360 }
361 self
362 }
363
364 pub fn output_repair_model(mut self, model: ModelRef) -> Self {
365 self.agent.output_repair_model = Some(model);
366 self
367 }
368
369 pub fn output_repair_model_settings(mut self, settings: ModelSettings) -> Self {
370 self.agent.output_repair_model_settings = Some(settings);
371 self
372 }
373
374 pub fn hook(mut self, hook: Arc<dyn RuntimeHook>) -> Self {
375 self.agent.hooks.push(hook);
376 self
377 }
378
379 pub fn max_cycles(mut self, max_cycles: u32) -> Self {
380 self.agent.max_cycles = Some(max_cycles);
381 self
382 }
383
384 pub fn no_tool_policy(mut self, policy: NoToolPolicy) -> Self {
385 self.agent.no_tool_policy = Some(policy);
386 self
387 }
388
389 pub fn tool_use_behavior(mut self, behavior: ToolUseBehavior) -> Self {
390 self.agent.tool_use_behavior = behavior;
391 self
392 }
393
394 pub fn tool_policy(mut self, tool_policy: ToolPolicy) -> Self {
395 self.agent.tool_policy = tool_policy;
396 self
397 }
398
399 pub fn sub_agent(mut self, id: impl AsRef<str>, config: impl Borrow<SubAgentConfig>) -> Self {
400 self.insert_sub_agent(id.as_ref(), config.borrow());
401 self
402 }
403
404 pub fn sub_agents<I, K, C>(mut self, sub_agents: I) -> Self
405 where
406 I: IntoIterator<Item = (K, C)>,
407 K: AsRef<str>,
408 C: Borrow<SubAgentConfig>,
409 {
410 for (id, config) in sub_agents {
411 self.insert_sub_agent(id.as_ref(), config.borrow());
412 }
413 self
414 }
415
416 pub fn metadata(mut self, key: impl Into<String>, value: serde_json::Value) -> Self {
417 self.agent.metadata.insert(key.into(), value);
418 self
419 }
420
421 pub fn build(self) -> Result<Agent, String> {
422 if self.agent.name.trim().is_empty() {
423 return Err("agent name cannot be empty".to_string());
424 }
425 if self.agent.instructions.trim().is_empty()
426 && self.agent.instruction_provider.is_none()
427 && self.agent.prompt_bundle_provider.is_none()
428 {
429 return Err("agent instructions cannot be empty".to_string());
430 }
431 if let Some(error) = self.sub_agent_error {
432 return Err(error);
433 }
434 if let Some(error) = self.output_validation_error {
435 return Err(error);
436 }
437 if self.agent.output_validation_enabled && self.agent.host_output_validator.is_none() {
438 return Err("enabled output validation requires a host_output_validator".to_string());
439 }
440 if self.agent.output_repair.is_some() && self.agent.host_output_validator.is_none() {
441 return Err("output_repair requires a host_output_validator".to_string());
442 }
443 Ok(self.agent)
444 }
445
446 fn insert_sub_agent(&mut self, id: &str, config: &SubAgentConfig) {
447 let normalized_id = trim_portable_whitespace(id);
448 if normalized_id.is_empty() {
449 self.sub_agent_error
450 .get_or_insert_with(|| "sub-agent id cannot be empty".to_string());
451 return;
452 }
453 if self.agent.sub_agents.contains_key(normalized_id) {
454 self.sub_agent_error.get_or_insert_with(|| {
455 format!("duplicate sub-agent id after normalization: {normalized_id}")
456 });
457 return;
458 }
459 self.agent
460 .sub_agents
461 .insert(normalized_id.to_string(), config.clone());
462 }
463}
464
465#[derive(Clone, Default)]
466pub enum ToolUseBehavior {
467 #[default]
468 RunLlmAgain,
469 StopOnFirstTool,
470 StopAtToolNames(Vec<String>),
471}