Skip to main content

hanzo_agent/
agent.rs

1//! Agent implementation
2
3use crate::errors::Result;
4use crate::result::RunResult;
5use crate::runner::{RunConfig, Runner};
6use crate::tool::Tool;
7use crate::types::ModelSettings;
8use std::sync::Arc;
9
10/// Instructions for the agent (system prompt)
11#[derive(Debug, Clone)]
12pub enum Instructions {
13    /// Static instructions
14    Static(String),
15
16    /// Dynamic instructions generated at runtime
17    /// TODO: Add support for dynamic instructions via function
18    Dynamic(String),
19}
20
21impl Instructions {
22    /// Get the instructions as a string
23    pub fn as_str(&self) -> &str {
24        match self {
25            Instructions::Static(s) | Instructions::Dynamic(s) => s,
26        }
27    }
28}
29
30impl From<String> for Instructions {
31    fn from(s: String) -> Self {
32        Instructions::Static(s)
33    }
34}
35
36impl From<&str> for Instructions {
37    fn from(s: &str) -> Self {
38        Instructions::Static(s.to_string())
39    }
40}
41
42/// An AI agent configured with instructions, tools, and settings
43///
44/// Agents are the core abstraction for building AI applications.
45/// They encapsulate a model, system prompt (instructions), tools, and other configuration.
46#[derive(Clone)]
47pub struct Agent {
48    /// The name of the agent
49    pub name: String,
50
51    /// Instructions (system prompt) for the agent
52    pub instructions: Option<Instructions>,
53
54    /// The model to use (e.g., "gpt-4", "claude-3-5-sonnet")
55    pub model: String,
56
57    /// Tools available to the agent
58    pub tools: Vec<Arc<dyn Tool>>,
59
60    /// Handoff agents (sub-agents the agent can delegate to)
61    pub handoffs: Vec<Agent>,
62
63    /// Model settings (temperature, max_tokens, etc.)
64    pub model_settings: ModelSettings,
65
66    /// Description for when this agent is used as a handoff
67    pub handoff_description: Option<String>,
68}
69
70impl Agent {
71    /// Create a new agent with the given name
72    pub fn new(name: impl Into<String>) -> Self {
73        Self {
74            name: name.into(),
75            instructions: None,
76            model: "gpt-4".to_string(),
77            tools: Vec::new(),
78            handoffs: Vec::new(),
79            model_settings: ModelSettings::default(),
80            handoff_description: None,
81        }
82    }
83
84    /// Create a builder for the agent
85    pub fn builder(name: impl Into<String>) -> AgentBuilder {
86        AgentBuilder::new(name)
87    }
88
89    /// Clone the agent with modifications
90    pub fn clone_with(&self) -> AgentBuilder {
91        AgentBuilder {
92            name: self.name.clone(),
93            instructions: self.instructions.clone(),
94            model: self.model.clone(),
95            tools: self.tools.clone(),
96            handoffs: self.handoffs.clone(),
97            model_settings: self.model_settings.clone(),
98            handoff_description: self.handoff_description.clone(),
99        }
100    }
101
102    /// Get the system prompt for the agent
103    pub fn system_prompt(&self) -> Option<&str> {
104        self.instructions.as_ref().map(|i| i.as_str())
105    }
106
107    /// Run the agent with the given input
108    ///
109    /// This is a convenience method that creates a default RunConfig.
110    /// For more control, use `Runner::run` directly.
111    pub async fn run(&self, input: impl Into<String>, config: &RunConfig) -> Result<RunResult> {
112        Runner::run(self, input.into(), config).await
113    }
114
115    /// Add a tool to the agent
116    pub fn with_tool(mut self, tool: impl Tool + 'static) -> Self {
117        self.tools.push(Arc::new(tool));
118        self
119    }
120
121    /// Add a handoff agent
122    pub fn with_handoff(mut self, agent: Agent) -> Self {
123        self.handoffs.push(agent);
124        self
125    }
126}
127
128impl std::fmt::Debug for Agent {
129    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
130        f.debug_struct("Agent")
131            .field("name", &self.name)
132            .field("model", &self.model)
133            .field("tools", &self.tools.len())
134            .field("handoffs", &self.handoffs.len())
135            .finish()
136    }
137}
138
139/// Builder for creating agents
140pub struct AgentBuilder {
141    name: String,
142    instructions: Option<Instructions>,
143    model: String,
144    tools: Vec<Arc<dyn Tool>>,
145    handoffs: Vec<Agent>,
146    model_settings: ModelSettings,
147    handoff_description: Option<String>,
148}
149
150impl AgentBuilder {
151    /// Create a new agent builder
152    pub fn new(name: impl Into<String>) -> Self {
153        Self {
154            name: name.into(),
155            instructions: None,
156            model: "gpt-4".to_string(),
157            tools: Vec::new(),
158            handoffs: Vec::new(),
159            model_settings: ModelSettings::default(),
160            handoff_description: None,
161        }
162    }
163
164    /// Set the agent instructions
165    pub fn instructions(mut self, instructions: impl Into<Instructions>) -> Self {
166        self.instructions = Some(instructions.into());
167        self
168    }
169
170    /// Set the model
171    pub fn model(mut self, model: impl Into<String>) -> Self {
172        self.model = model.into();
173        self
174    }
175
176    /// Add a tool
177    pub fn tool(mut self, tool: impl Tool + 'static) -> Self {
178        self.tools.push(Arc::new(tool));
179        self
180    }
181
182    /// Add multiple tools
183    pub fn tools(mut self, tools: Vec<Arc<dyn Tool>>) -> Self {
184        self.tools.extend(tools);
185        self
186    }
187
188    /// Add a handoff agent
189    pub fn handoff(mut self, agent: Agent) -> Self {
190        self.handoffs.push(agent);
191        self
192    }
193
194    /// Set model settings
195    pub fn model_settings(mut self, settings: ModelSettings) -> Self {
196        self.model_settings = settings;
197        self
198    }
199
200    /// Set handoff description
201    pub fn handoff_description(mut self, desc: impl Into<String>) -> Self {
202        self.handoff_description = Some(desc.into());
203        self
204    }
205
206    /// Build the agent
207    pub fn build(self) -> Agent {
208        Agent {
209            name: self.name,
210            instructions: self.instructions,
211            model: self.model,
212            tools: self.tools,
213            handoffs: self.handoffs,
214            model_settings: self.model_settings,
215            handoff_description: self.handoff_description,
216        }
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223
224    #[test]
225    fn test_agent_builder() {
226        let agent = Agent::builder("test")
227            .instructions("You are a helpful assistant")
228            .model("gpt-4")
229            .build();
230
231        assert_eq!(agent.name, "test");
232        assert_eq!(agent.model, "gpt-4");
233        assert!(agent.system_prompt().is_some());
234    }
235
236    #[test]
237    fn test_agent_clone_with() {
238        let agent = Agent::builder("test")
239            .instructions("Original instructions")
240            .build();
241
242        let modified = agent.clone_with().instructions("New instructions").build();
243
244        assert_eq!(modified.name, "test");
245        assert_eq!(modified.system_prompt(), Some("New instructions"));
246    }
247}