Skip to main content

vv_agent/
agent.rs

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}