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::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}