Skip to main content

open_agent/tools/
factory.rs

1/// Create a tool using the builder pattern (convenience function).
2///
3/// This is the recommended way to create tools. It returns a [`ToolBuilder`] that
4/// allows you to fluently configure the tool's schema and handler.
5///
6/// ## Typical Usage Pattern
7///
8/// ```text
9/// tool(name, description)
10///     .param(name, type)  // Add parameters (optional, can repeat)
11///     .build(handler)     // Provide handler and create Tool
12/// ```
13///
14/// ## Why Use This Instead of Tool::new?
15///
16/// - **More readable**: The builder pattern reads like natural language
17/// - **Incremental schema building**: Add parameters one at a time
18/// - **Flexible**: Can conditionally add parameters or use `.schema()` for complex cases
19/// - **Type-safe**: Method chaining ensures you can't forget the handler
20///
21/// ## Parameters
22///
23/// - `name`: Unique identifier for the tool (snake_case recommended)
24/// - `description`: Human-readable explanation of what the tool does
25///
26/// Both parameters accept any type that implements `Into<String>`, so you can
27/// pass string literals, `String` values, or anything else convertible to String.
28///
29/// ## Examples
30///
31/// ### Basic Calculator Tool
32///
33/// ```rust,no_run
34/// use open_agent::tool;
35/// use serde_json::json;
36///
37/// let add_tool = tool("add", "Add two numbers")
38///     .param("a", "number")
39///     .param("b", "number")
40///     .build(|args| async move {
41///         let a = args.get("a")
42///             .and_then(|v| v.as_f64())
43///             .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'a' must be a number"))?;
44///         let b = args.get("b")
45///             .and_then(|v| v.as_f64())
46///             .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'b' must be a number"))?;
47///         Ok(json!({"result": a + b}))
48///     });
49/// ```
50///
51/// ### Tool with External HTTP Client
52///
53/// ```rust,no_run
54/// use open_agent::{tool, Error};
55/// use serde_json::json;
56/// # use std::sync::Arc;
57///
58/// // Shared HTTP client (example - use your actual HTTP client)
59/// # struct HttpClient;
60/// # impl HttpClient {
61/// #     fn new() -> Self { HttpClient }
62/// #     async fn get(&self, url: &str) -> Result<String, Box<dyn std::error::Error>> {
63/// #         Ok("response".to_string())
64/// #     }
65/// # }
66/// let http_client = Arc::new(HttpClient::new());
67///
68/// let fetch_tool = tool("fetch_url", "Fetch content from a URL")
69///     .param("url", "string")
70///     .build(move |args| {
71///         let client = http_client.clone();
72///         async move {
73///             let url = args["url"].as_str().unwrap_or("");
74///             let content = client.get(url).await
75///                 .map_err(|e| Error::tool(format!("Failed to fetch: {}", e)))?;
76///             Ok(json!({"content": content}))
77///         }
78///     });
79/// ```
80///
81/// ### Tool with Complex Schema
82///
83/// ```rust,no_run
84/// use open_agent::tool;
85/// use serde_json::json;
86///
87/// let search_tool = tool("search", "Search for information")
88///     .schema(json!({
89///         "query": {
90///             "type": "string",
91///             "description": "Search query"
92///         },
93///         "filters": {
94///             "type": "object",
95///             "description": "Optional filters",
96///             "optional": true,
97///             "properties": {
98///                 "date_from": {"type": "string"},
99///                 "date_to": {"type": "string"}
100///             }
101///         },
102///         "max_results": {
103///             "type": "integer",
104///             "default": 10,
105///             "optional": true
106///         }
107///     }))
108///     .build(|args| async move {
109///         // Implementation
110///         Ok(json!({"results": []}))
111///     });
112/// ```
113///
114/// ### Conditional Parameter Addition
115///
116/// ```rust,no_run
117/// use open_agent::tool;
118/// use serde_json::json;
119///
120/// # let enable_advanced = true;
121/// let mut builder = tool("process", "Process data")
122///     .param("input", "string");
123///
124/// // Conditionally add parameters
125/// if enable_advanced {
126///     builder = builder.param("advanced_mode", "boolean");
127/// }
128///
129/// let my_tool = builder.build(|args| async move {
130///     Ok(json!({"status": "processed"}))
131/// });
132/// ```
133///
134/// ### Integration with Agent
135///
136/// ```rust,no_run
137/// use open_agent::{Client, AgentOptions, tool};
138/// use serde_json::json;
139///
140/// # async fn example() -> open_agent::Result<()> {
141/// let weather_tool = tool("get_weather", "Get weather for a location")
142///     .param("location", "string")
143///     .build(|args| async move {
144///         Ok(json!({"temp": 72, "conditions": "sunny"}))
145///     });
146///
147/// let options = AgentOptions::builder()
148///     .model("gpt-4")
149///     .base_url("http://localhost:1234/v1")
150///     .tool(weather_tool)
151///     .build()?;
152///
153/// let client = Client::new(options)?;
154/// // Client can now use the tool when responding to queries
155/// # Ok(())
156/// # }
157/// ```
158///
159/// ## See Also
160///
161/// - [`Tool::new`] - Direct constructor if you prefer not using the builder
162/// - [`ToolBuilder`] - The builder type returned by this function
163/// - [`Tool`] - The final tool type produced by `.build()`
164pub fn tool(name: impl Into<String>, description: impl Into<String>) -> ToolBuilder {
165    ToolBuilder::new(name, description)
166}