open_agent/types/agent_options.rs
1/// Configuration options for an AI agent instance.
2///
3/// `AgentOptions` controls all aspects of agent behavior including model selection,
4/// conversation management, tool usage, and lifecycle hooks. This struct should be
5/// constructed using [`AgentOptions::builder()`] rather than direct instantiation
6/// to ensure required fields are validated.
7///
8/// # Architecture
9///
10/// The options are organized into several functional areas:
11///
12/// - **Model Configuration**: `model`, `base_url`, `api_key`, `temperature`, `max_tokens`
13/// - **Conversation Control**: `system_prompt`, `max_turns`, `timeout`
14/// - **Tool Management**: `tools`, `auto_execute_tools`, `max_tool_iterations`
15/// - **Lifecycle Hooks**: `hooks` for monitoring and interception
16///
17/// # Thread Safety
18///
19/// Tools are wrapped in `Arc<Tool>` to allow efficient cloning and sharing across
20/// threads, as agents may need to be cloned for parallel processing.
21///
22/// # Examples
23///
24/// ```no_run
25/// use open_agent::AgentOptions;
26///
27/// let options = AgentOptions::builder()
28/// .model("qwen2.5-32b-instruct")
29/// .base_url("http://localhost:1234/v1")
30/// .system_prompt("You are a helpful coding assistant")
31/// .max_turns(5)
32/// .temperature(0.7)
33/// .build()
34/// .expect("Valid configuration");
35/// ```
36#[derive(Clone)]
37pub struct AgentOptions {
38 /// System prompt that defines the agent's behavior and personality.
39 ///
40 /// This is sent as the first message in the conversation to establish
41 /// context and instructions. Can be empty if no system-level guidance
42 /// is needed.
43 system_prompt: String,
44
45 /// Model identifier for the LLM to use (e.g., "qwen2.5-32b-instruct", "gpt-4").
46 ///
47 /// This must match a model available at the configured `base_url`.
48 /// Different models have varying capabilities for tool use, context
49 /// length, and response quality.
50 model: String,
51
52 /// OpenAI-compatible API endpoint URL (e.g., "http://localhost:1234/v1").
53 ///
54 /// The SDK communicates using the OpenAI chat completions API format,
55 /// which is widely supported by local inference servers (LM Studio,
56 /// llama.cpp, vLLM) and cloud providers.
57 base_url: String,
58
59 /// API authentication key for the provider.
60 ///
61 /// Many local servers don't require authentication, so the default
62 /// "not-needed" is often sufficient. For cloud providers like OpenAI,
63 /// set this to your actual API key.
64 api_key: String,
65
66 /// Maximum number of conversation turns (user message + assistant response = 1 turn).
67 ///
68 /// This limits how long a conversation can continue. In auto-execution mode
69 /// with tools, this prevents infinite loops. Set to 1 for single-shot
70 /// interactions or higher for multi-turn conversations.
71 max_turns: u32,
72
73 /// Maximum tokens the model should generate in a single response.
74 ///
75 /// `None` uses the provider's default. Lower values constrain response
76 /// length, which can be useful for cost control or ensuring concise answers.
77 /// Note this is separate from the model's context window size.
78 max_tokens: Option<u32>,
79
80 /// Sampling temperature for response generation (typically 0.0 to 2.0).
81 ///
82 /// - 0.0: Deterministic, always picks most likely tokens
83 /// - 0.7: Balanced creativity and consistency (default)
84 /// - 1.0+: More random and creative responses
85 ///
86 /// Lower temperatures are better for factual tasks, higher for creative ones.
87 temperature: f32,
88
89 /// HTTP request timeout in seconds.
90 ///
91 /// Maximum time to wait for the API to respond. Applies per API call,
92 /// not to the entire conversation. Increase for slower models or when
93 /// expecting long responses.
94 timeout: u64,
95
96 /// Tools available for the agent to use during conversations.
97 ///
98 /// Tools are wrapped in `Arc` for efficient cloning. When the agent
99 /// receives a tool use request, it looks up the tool by name in this
100 /// vector. Empty by default.
101 tools: Vec<Arc<Tool>>,
102
103 /// Whether to automatically execute tools and continue the conversation.
104 ///
105 /// - `true`: SDK automatically executes tool calls and sends results back
106 /// to the model, continuing until no more tools are requested
107 /// - `false`: Tool calls are returned to the caller, who must manually
108 /// execute them and provide results
109 ///
110 /// Auto-execution is convenient but gives less control. Manual execution
111 /// allows for approval workflows and selective tool access.
112 auto_execute_tools: bool,
113
114 /// Maximum iterations of tool execution in automatic mode.
115 ///
116 /// Prevents infinite loops where the agent continuously requests tools.
117 /// Each tool execution attempt counts as one iteration. Only relevant
118 /// when `auto_execute_tools` is true.
119 max_tool_iterations: u32,
120
121 /// Lifecycle hooks for observing and intercepting agent operations.
122 ///
123 /// Hooks allow you to inject custom logic at various points:
124 /// - Before/after API requests
125 /// - Tool execution interception
126 /// - Response streaming callbacks
127 ///
128 /// Useful for logging, metrics, debugging, and implementing custom
129 /// authorization logic.
130 hooks: Hooks,
131}
132
133/// Custom Debug implementation to prevent sensitive data leakage.
134///
135/// We override the default Debug implementation because:
136/// 1. The `api_key` field may contain sensitive credentials that shouldn't
137/// appear in logs or error messages
138/// 2. The `tools` vector contains Arc-wrapped closures that don't debug nicely,
139/// so we show a count instead
140///
141/// This ensures that debug output is safe for logging while remaining useful
142/// for troubleshooting.
143impl std::fmt::Debug for AgentOptions {
144 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145 f.debug_struct("AgentOptions")
146 .field("system_prompt", &self.system_prompt)
147 .field("model", &self.model)
148 .field("base_url", &self.base_url)
149 // Mask API key to prevent credential leakage in logs
150 .field("api_key", &"***")
151 .field("max_turns", &self.max_turns)
152 .field("max_tokens", &self.max_tokens)
153 .field("temperature", &self.temperature)
154 .field("timeout", &self.timeout)
155 // Show tool count instead of trying to debug Arc<Tool> contents
156 .field("tools", &format!("{} tools", self.tools.len()))
157 .field("auto_execute_tools", &self.auto_execute_tools)
158 .field("max_tool_iterations", &self.max_tool_iterations)
159 .field("hooks", &self.hooks)
160 .finish()
161 }
162}
163
164/// Default values optimized for common single-turn use cases.
165///
166/// These defaults are chosen to:
167/// - Require explicit configuration of critical fields (model, base_url)
168/// - Provide safe, sensible defaults for optional fields
169/// - Work with local inference servers that don't need authentication
170impl Default for AgentOptions {
171 fn default() -> Self {
172 Self {
173 // Empty string forces users to explicitly set context
174 system_prompt: String::new(),
175 // Empty string forces users to explicitly choose a model
176 model: String::new(),
177 // Empty string forces users to explicitly configure the endpoint
178 base_url: String::new(),
179 // Most local servers (LM Studio, llama.cpp) don't require auth
180 api_key: "not-needed".to_string(),
181 // Default to single-shot interaction; users opt into conversations
182 max_turns: 1,
183 // 4096 is a reasonable default that works with most models
184 // while preventing runaway generation costs
185 max_tokens: Some(4096),
186 // 0.7 balances creativity with consistency for general use
187 temperature: 0.7,
188 // 60 seconds handles most requests without timing out prematurely
189 timeout: 60,
190 // No tools by default; users explicitly add capabilities
191 tools: Vec::new(),
192 // Manual tool execution by default for safety and control
193 auto_execute_tools: false,
194 // 5 iterations prevent infinite loops while allowing multi-step workflows
195 max_tool_iterations: 5,
196 // Empty hooks for no-op behavior
197 hooks: Hooks::new(),
198 }
199 }
200}
201
202impl AgentOptions {
203 /// Creates a new builder for constructing [`AgentOptions`].
204 ///
205 /// The builder pattern is used because:
206 /// 1. Some fields are required (model, base_url) and need validation
207 /// 2. Many fields have sensible defaults that can be overridden
208 /// 3. The API is more discoverable and readable than struct initialization
209 ///
210 /// # Example
211 ///
212 /// ```no_run
213 /// use open_agent::AgentOptions;
214 ///
215 /// let options = AgentOptions::builder()
216 /// .model("qwen2.5-32b-instruct")
217 /// .base_url("http://localhost:1234/v1")
218 /// .build()
219 /// .expect("Valid configuration");
220 /// ```
221 pub fn builder() -> AgentOptionsBuilder {
222 AgentOptionsBuilder::default()
223 }
224
225 /// Returns the system prompt.
226 pub fn system_prompt(&self) -> &str {
227 &self.system_prompt
228 }
229
230 /// Returns the model identifier.
231 pub fn model(&self) -> &str {
232 &self.model
233 }
234
235 /// Returns the base URL.
236 pub fn base_url(&self) -> &str {
237 &self.base_url
238 }
239
240 /// Returns the API key.
241 pub fn api_key(&self) -> &str {
242 &self.api_key
243 }
244
245 /// Returns the maximum number of conversation turns.
246 pub fn max_turns(&self) -> u32 {
247 self.max_turns
248 }
249
250 /// Returns the maximum tokens setting.
251 pub fn max_tokens(&self) -> Option<u32> {
252 self.max_tokens
253 }
254
255 /// Returns the sampling temperature.
256 pub fn temperature(&self) -> f32 {
257 self.temperature
258 }
259
260 /// Returns the HTTP timeout in seconds.
261 pub fn timeout(&self) -> u64 {
262 self.timeout
263 }
264
265 /// Returns a reference to the tools vector.
266 pub fn tools(&self) -> &[Arc<Tool>] {
267 &self.tools
268 }
269
270 /// Returns whether automatic tool execution is enabled.
271 pub fn auto_execute_tools(&self) -> bool {
272 self.auto_execute_tools
273 }
274
275 /// Returns the maximum tool execution iterations.
276 pub fn max_tool_iterations(&self) -> u32 {
277 self.max_tool_iterations
278 }
279
280 /// Returns a reference to the hooks configuration.
281 pub fn hooks(&self) -> &Hooks {
282 &self.hooks
283 }
284}